访问者模式/Visitor
意图/适用场景:
访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开来,使得操作集合可以相对自由地演化。
数据结构的每一个节点者可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象而反过来执行节点对象的操作。这样的过程叫做“双重分派”。
访问者模式的功能在于,你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
假如现在有一个对象的聚集,是一个树状结构的集合,而且树的结构非常的复杂,有很多层次,不光有叶节点,还有很多枝节点。而且各个节点的类型也不相同,所定义的方法也不相同。
现在你想实现这样一种功能:遍历所有的叶节点,让每一个节点实现某一种功能(function),但是不同的节点种这件事件所需要的步骤是不一样的,比如A节点需要调用方法operationX()在,而B节点需要同时调用方法operationY()和operationZ(),…… 要想实现这一功能可不是一件容易的事。
这时,访问者模式就可以帮上你的忙。这一模式总的想法就是再定义一个访问者角色,一个访问者就是为实现这样一种功能而定义的,它为每一个节点定义一个方法function(),在这个方法中再去调用相应节点的operation(),从而完成这一功能所需要的逻辑。比如,在为A节点定义的方法functionA()中,就调用A.operationX();在为B节点定义的方法functionB()中,就调用B.operationY()和B.operationZ()。
UML:
参与者:
- 元素(Element)/节点(Node):—定义一个Accept操作,它以一个访问者为参数。
- 具体元素(ConcreteElement):实现Accept操作,该操作以一个访问者为参数。
- 访问者(Visitor):—为该对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。
- 具体访问者(ConcreteVisitor):—实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断乃是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果。
- 对象结构(ObjectStructure):能枚举它的元素。可以提供一个高层的接口以允许该访问者访问它的元素。可以是一个复合或是一个集合,如一个列表或一个无序集合。
要点:
下面是访问者模式的一些优缺点:
- 访问者模式使得易于增加新的操作
- 访问者使得增加依赖于复杂对象结构的构件的操作/功能变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。
- 访问者集中相关的操作而分离无关的操作
- 相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。
- 增加新的ConcreteElement类很困难
- Visitor模式使得难以增加新的Element的子类。每添加一个新的ConcreteElement都要在Vistor中添加一个新的抽象操作,并在每一个ConcretVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,但这与其说是一个规律还不如说是一种例外。所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢还是构成该结构的各个对象的类。如果老是有新的ConcretElement类加入进来的话,Vistor类层次将变得难以维护。在这种情况下,直接在构成该结构的类中定义这些操作可能更容易一些。如果Element类层次是稳定的,而你不断地增加操作获修改算法,访问者模式可以帮助你管理这些改动。
- 通过类层次进行访问
- 在Element树状结构中,即使树有很多层,即有很多中间层的枝节点,这种节点本身是一个集合,访问者模式也能胜任。但与之相比,另一个与集合类有关的模式“迭代模式”就不具备这一功能,如果以树状结构来衡量的话,它只能处理一层的树。
- 累积状态
- 当访问者访问对象结构中的每一个元素时,它可能会累积状态。如果没有访问者,这一状态将作为额外的参数传递给进行遍历的操作,或者定义为全局变量。
- 破坏封装
- 访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。
应用实例:
《设计模式》一书中介绍本模式时引用的编译器的例子很好,请参考。
示例代码:
[java]
// Source code from file:Element.java
packagedesignPatterns.Visitor;
publicinterface Element {
publicvoid accept(Visitor visitor);
}
// Source code from file:ElementA.java
packagedesignPatterns.Visitor;
publicclass ElementA implements Element {
publicvoid accept(Visitor visitor) {
visitor.visitA(this);
}
publicvoid operationA1() {}
}
// Source code from file:ElementB.java
packagedesignPatterns.Visitor;
publicclass ElementB implements Element {
publicvoid accept(Visitor visitor) {
visitor.visitB(this);
}
publicvoid operationB1() {}
publicvoid operationB2() {}
}
// Source code from file:ElementC.java
packagedesignPatterns.Visitor;
publicclass ElementC implements Element {
publicvoid accept(Visitor visitor) {
visitor.visitC(this);
}
publicvoid operationC1() {}
publicvoid operationC3() {}
}
// Source code from file:ObjectStructure.java
packagedesignPatterns.Visitor;
import java.util.Enumeration;
import java.util.Vector;
publicclass ObjectStructure {
privateVector elements = new Vector();
publicvoid addElement(Element e) {
elements.addElement(e);
}
publicvoid action(Visitor visitor) {
for(Enumeration enu = elements.elements(); enu.hasMoreElements(); ) {
Elemente = (Element)enu.nextElement();
e.accept(visitor);
}
}
}
// Source code from file:User.java
packagedesignPatterns.Visitor;
publicclass User {
publicstatic void main(String[] args) {
ObjectStructure obs =new ObjectStructure();
obs.addElement(newElementA());
obs.addElement(newElementB());
obs.addElement(newElementC());
Visitor visitorA =new VisitorA();
Visitor visitorB =new VisitorB();
obs.action(visitorA);
obs.action(visitorB);
}
}
// Source code from file:Visitor.java
packagedesignPatterns.Visitor;
publicinterface Visitor {
publicvoid visitA(ElementA element);
publicvoid visitB(ElementB element);
publicvoid visitC(ElementC element);
}
// Source code from file:VisitorA.java
packagedesignPatterns.Visitor;
publicclass VisitorA implements Visitor {
publicvoid visitA(ElementA element) {
element.operationA1();
}
publicvoid visitB(ElementB element) {
element.operationB1();
}
publicvoid visitC(ElementC element) {
element.operationC1();
}
}
// Source code from file:VisitorB.java
packagedesignPatterns.Visitor;
publicclass VisitorB implements Visitor {
publicvoid visitA(ElementA element) {}
publicvoid visitB(ElementB element) {
element.operationB2();
}
publicvoid visitC(ElementC element) {
element.operationC3();
}
}
[/java]