访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。
《设计模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于java中的collection概念了。
以下是访问者模式的组成结构:
1) 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
2) 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。
3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。
4) 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。
5) 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
来张类图就能更加清晰的看清访问者模式的结构了。
abstract class Element {
public abstract void accept(IVisitor visitor);
public abstract void doSomething();
}
interface IVisitor {
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}
class ConcreteElement1 extends Element {
public void doSomething(){
System.out.println("这是元素1");
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
class ConcreteElement2 extends Element {
public void doSomething(){
System.out.println("这是元素2");
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
class Visitor implements IVisitor {
public void visit(ConcreteElement1 el1) {
el1.doSomething();
}
public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}
class ObjectStruture {
public static List<Element> getList(){
List<Element> list = new ArrayList<Element>();
Random ran = new Random();
for(int i=0; i<10; i++){
int a = ran.nextInt(100);
if(a>50){
list.add(new ConcreteElement1());
}else{
list.add(new ConcreteElement2());
}
}
return list;
}
}
public class Client {
public static void main(String[] args){
List<Element> list = ObjectStruture.getList();
for(Element e: list){
e.accept(new Visitor());
}
}
}
双重分派,首先在客户程序中将具体访问者模式作为参数传递给具体元素角色(加亮的地方所示)。这便完成了一次分派。进入具体元素角色后,具体元素角色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行(加亮的地方所示)。这便完成了第二次分派。
《设计模式》一书中给出了访问者模式适用的情况:
1) 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4)定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
上面内容多是网络参考,下面是自己的一点理解。
1)“一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。”这里说的情况是这些类对象是在一个容器里面的情况,访问者模式的精髓我认为就在visitor.visit(this);这行代码,该行代码所在的上下文环境是在具体的element里面,所以this就有了真实的类型,然后根据visit实现的不同的接口利用重载的特性找到对应的方法,从而省去dynamic_cast和instantof等低效率的方法。试想如果没有visitor模式遇到改场景会怎么做呢?(1)每个element有一个getType()方法判断是什么类类型,然后再根据type来处理。(2)使用instanceof等方法确定类的具体类型,然后在处理。
2)“需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。”这就是职责转移,上面一点说不同的对象在同一个容器中,那么不同的元素在各自的容器中的时候是否可以使用访问者呢?从这个适用点来看是可以的,目的就是为了把这些无关的代码放在别的地方。但是,如果不同的对象在各自不同的容器中,“访问者”访问到已知类型元素后直接执行不同的操作就可以了,还是访问者么?我理解访问者模式的重点还是上面那个“对这些对象实施一些依赖于其具体类的操作”,所以这点可以说是附加的优点。
3) “当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。”这个是通过不同的visitor来实现的,所有对element的操作都在visitor中,从而控制住了element的操作权限。但是,这样就会增加了visitor的类。
4)“定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。”,也可以说是“开闭原则”,但开闭原则中谁是那个可以“开”的,谁是那个可以“闭”的要根据需求和场景来定。访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。
参考:
http://blog.csdn.net/zhengzhb/article/details/7489639
http://dev.yesky.com/82/2036582.shtml