描述
定义
表示一个作用于某对象结构中的各元素的操作,可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
类型
对象行为型模式
动机
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
UML类图
实现
主要角色
- Visitor:抽象访问者
- 为对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。
- ConcreteVisitor:具体访问者
- 实现了抽象访问者声明的方法,每一个操作作用于访问对象结构中一种类型的元素。
- Element:抽象元素
- 定义一个Accept方法,该方法通常以一个抽象访问者作为参数。
- ConcreteElement:具体元素
- 实现Accept方法,调用访问者的访问方法以便完成一个元素的操作。
- ObjectStructure:对象结构
- 能枚举它的元素。
- 提供一个高层的接口以允许访问者访问它的元素。
- 可以是一个复合或是一个集合,如一个列表或一个无序集合。
示例
-
Visitor:抽象访问者
interface Visitor { void visit(ConcreteElement1 e); void visit(ConcreteElement2 e); }
-
ConcreteVisitor:具体访问者
class ConcreteVisitor implements Visitor { @override public void visit(ConcreteElement1 e) { System.out.println("访问ConcreteElement1"); e.doSomething(); } @override public void visit(ConcreteElement2 e) { System.out.println("访问ConcreteElement2"); e.doSomething(); } }
-
Element:抽象元素
abstract class Element { public abstract void accept(Visitor visitor); public abstract void doSomething(); }
-
ConcreteElement:具体元素。visit操作的执行决定于Visitor和Element的类型,双分派使得访问者可以对每一个类元素请求不同的操作。
class ConcreteElement1 extends Element { @override public void doSomething(){ System.out.println("这是元素1"); } @override public void accept(Visitor visitor) { visitor.visit(this); } } class ConcreteElement2 extends Element { @override public void doSomething(){ System.out.println("这是元素2"); } @override public void accept(Visitor visitor) { visitor.visit(this); } }
-
ObjectStructure:对象结构。可以将遍历责任放到下面三个地方中的任意一个:对象结构中,访问者中,或一个独立的迭代器对象中。通常由对象结构负责迭代。
class ObjectStruture { List<Element> list = new ArrayList<Element>(); public List<Element> getElements() { return list; } public void addElement(Element e) { list.add(e); } public void removeElement(Element e) { list.remove(e); } }
-
Client:客户类。
public class Client { public static void main(String[] args) { ObjectStruture os = new ObjectStruture(); Element e1 = new ConcreteElement1(); os.add(e1); Element e2 = new ConcreteElement2(); os.add(e2); List<Element> list = os.getElements(); for(Element e: list){ e.accept(new Visitor()); } } }
适用场景
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,避免让这些操作“污染”这些对象的类。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
优点
- 增加新的访问操作十分方便,符合开闭原则。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。
- 访问者集中相关的操作而分离无关的操作,符合单一职责原则。相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。
- 通过类层次进行遍历访问。一个迭代器可以通过调用节点对象的特定操作来遍历整个对象结构,同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。访问者可以访问不具有相同父类的对象。
缺点
- 增加新的ConcreteElement类很困难。每添加一个新的ConcreteElement都要在Visitor中添加一个新的抽象操作,并在每一个ConcreteVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现。
- 破坏封装。元素对象有时候必须暴露一些自己的内部操作和状态,否则无法供访问者访问,这破坏了元素的封装性。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
相关模式
- Composite:访问者可以用于对一个由Composite模式定义的对象结构进行操作。
- Interpreter:访问者可以用于解释。