访问者模式

《大话设计模式》第28章读书笔记,介绍访问者模式

Posted by GrayWind on November 22, 2018

访问者模式(Visitor)表示一个作用于某个对象结构中的各元素的操作。它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。

visitor

Visitor类,为该对象结果中ConcreteElement的每一个类声明一个Visit操作

public interface Visitor {
	public void visitConcreteElementA(ConcreteElementA concreteElementA);

	public void visitConcreteElementA(ConcreteElementB concreteElementB);
}

ConcreteVisitor1和ConcreteVisitor2类,具体访问者,实现每个由Visitor声明的操作。每个操作实现算法的一部分,而该算法片断乃是对应于结构中对象的类。

public class ConcreteVisitor1 implements Visitor {

	@Override
	public void visitConcreteElementA(ConcreteElementA concreteElementA) {
		System.out.printf("%s被%s访问\n", concreteElementA.getClass().getName(), this.getClass().getName());
	}

	@Override
	public void visitConcreteElementB(ConcreteElementB concreteElementB) {
		System.out.printf("%s被%s访问\n", concreteElementB.getClass().getName(), this.getClass().getName());
	}

}

public class ConcreteVisitor2 implements Visitor {

	@Override
	public void visitConcreteElementA(ConcreteElementA concreteElementA) {
		System.out.printf("%s被%s访问\n", concreteElementA.getClass().getName(), this.getClass().getName());
	}

	@Override
	public void visitConcreteElementB(ConcreteElementB concreteElementB) {
		System.out.printf("%s被%s访问\n", concreteElementB.getClass().getName(), this.getClass().getName());
	}

}

Element类,定义一个Accept操作,它以一个访问者为参数

public interface Element {
	public void accept(Visitor visitor);
}

ConcreteElementA和ConcreteElementB类,具体元素,实现Accept操作,利用双分派技术,实现处理与数据结构的分离。

public class ConcreteElementA implements Element {

	@Override
	public void accept(Visitor visitor) {
		visitor.visitConcreteElementA(this);
	}

	// 其他相关方法
	public void operationA() {
	}
}

public class ConcreteElementB implements Element {

	@Override
	public void accept(Visitor visitor) {
		visitor.visitConcreteElementB(this);
	}

	// 其他相关方法
	public void operationB() {
	}
}

ObjectStructure类,能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素

public class ObjectStructure {
	private List<Element> elements = new ArrayList<>();

	public void attach(Element element) {
		elements.add(element);
	}

	public void detach(Element element) {
		elements.remove(element);
	}

	public void accept(Visitor visitor) {
		for (Element e : elements) {
			e.accept(visitor);
		}
	}
}

客户端代码

public class VisitorTest {
	public static void main(String[] args) {
		ObjectStructure o = new ObjectStructure();
		o.attach(new ConcreteElementA());
		o.attach(new ConcreteElementB());

		ConcreteVisitor1 v1 = new ConcreteVisitor1();
		ConcreteVisitor2 v2 = new ConcreteVisitor2();

		o.accept(v1);
		o.accept(v2);
	}
}

运行结果

graywind.design.visitor.ConcreteElementA被graywind.design.visitor.ConcreteVisitor1访问
graywind.design.visitor.ConcreteElementB被graywind.design.visitor.ConcreteVisitor1访问
graywind.design.visitor.ConcreteElementA被graywind.design.visitor.ConcreteVisitor2访问
graywind.design.visitor.ConcreteElementB被graywind.design.visitor.ConcreteVisitor2访问

从上面的结构代码中可以看出,增加一个ConcreteVisitor也就是具体算法的代价较低,但增加一个ConcreteElement也就是具体元素的代价很高,需要改动Visitor接口和所有的ConcreteVisitor。所以,访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地烟花。它的目的是要把处理从数据结构中分离出来。很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式比较合适,因为算法操作的增加变得容易。反之,如果系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合使用访问者模式,因为访问者模式的决定就是增加新的数据结构变得困难了。

因为现实中很少有不变的数据结构,所以访问者模式很少被使用。下面这段话中,因为人的性别是固定的,所以可以使用访问者模式

男人成功时,背后多半有一个伟大的女人。
女人成功时,背后大多有一个不成功的男人。
男人失败时,闷头喝酒,谁也不用劝。
女人失败时,眼泪汪汪,谁也劝不了。
男人恋爱时,凡事不懂也要装懂。
女人恋爱时,遇事懂也装作不懂。
男人结婚时,感慨道:恋爱游戏终结时,‘有妻徒刑’遥无期。
女人结婚时,欣慰曰:爱情长跑路漫漫,婚姻保险保平安。

状态抽象类相当于Visitor,人抽象类相当于Element

public interface Action {
	// 得到男人的结论或反应
	public void getManConclusion(Man concreteElementA);

	// 得到男人的结论或反应
	public void getWomanConclusion(Woman concreteElementB);
}

public interface Person {
	public void accept(Action visitor);
}

具体状态类,对应ConcreteVisitor

public class Success implements Action {

	@Override
	public void getManConclusion(Man concreteElementA) {
		System.out.printf("%s%s时,背后多半有一个伟大的女人。\n", concreteElementA.toString(), this.toString());
	}

	@Override
	public void getWomanConclusion(Woman concreteElementB) {
		System.out.printf("%s%s时,背后大多有一个不成功的男人。\n", concreteElementB.toString(), this.toString());
	}
	
	public String toString() {
		return "成功";
	}

}

public class Failing implements Action {

	@Override
	public void getManConclusion(Man concreteElementA) {
		System.out.printf("%s%s时,闷头喝酒,谁也不用劝。\n", concreteElementA.toString(), this.toString());
	}

	@Override
	public void getWomanConclusion(Woman concreteElementB) {
		System.out.printf("%s%s时,眼泪汪汪,谁也劝不了。\n", concreteElementB.toString(), this.toString());
	}
	
	public String toString() {
		return "失败";
	}

}

public class Amativeness implements Action {

	@Override
	public void getManConclusion(Man concreteElementA) {
		System.out.printf("%s%s时,凡事不懂也要装懂。\n", concreteElementA.toString(), this.toString());
	}

	@Override
	public void getWomanConclusion(Woman concreteElementB) {
		System.out.printf("%s%s时,遇事懂也装作不懂。\n", concreteElementB.toString(), this.toString());
	}
	
	public String toString() {
		return "恋爱";
	}

}

男人和女人类,相当于ConcreteElement

public class Man implements Person {

	@Override
	public void accept(Action visitor) {
		visitor.getManConclusion(this);
	}
	
	public String toString() {
		return "男人";
	}

}

public class Woman implements Person {

	@Override
	public void accept(Action visitor) {
		visitor.getWomanConclusion(this);
	}
	
	public String toString() {
		return "女人";
	}

}

对象结构类,由于总是需要男人与女人在不同状态的对比,所以我们需要一个对象结构类来针对不同的状态遍历男人与女人,得到不同的反应

public class ObjectStructure {
	private List<Person> persons = new ArrayList<>();

	public void attach(Person element) {
		persons.add(element);
	}

	public void detach(Person element) {
		persons.remove(element);
	}

	public void display(Action visitor) {
		for (Person e : persons) {
			e.accept(visitor);
		}
	}
}

客户端代码

public class Test {
	public static void main(String[] args) {
		ObjectStructure o = new ObjectStructure();
        o.attach(new Man());
        o.attach(new Woman());

        Success v1 = new Success();
        o.display(v1);

        Failing v2 = new Failing();
        o.display(v2);

	}
}

如果我们需要增加“结婚”这个状态,只需要新增一个状态类

public class Marriage implements Action {

	@Override
	public void getManConclusion(Man concreteElementA) {
		System.out.printf("%s%s时,感慨道:恋爱游戏终结时,‘有妻徒刑’遥无期。\n", concreteElementA.toString(), this.toString());
	}

	@Override
	public void getWomanConclusion(Woman concreteElementB) {
		System.out.printf("%s%s时,欣慰曰:爱情长跑路漫漫,婚姻保险保平安。\n", concreteElementB.toString(), this.toString());
	}
	
	public String toString() {
		return "结婚";
	}

}

客户端增加

        Marriage v4 = new Marriage();
        o.display(v4);