5.11-Visitor-访问者

目的

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

示例

考虑一个编译器,它将源程序表示为一个抽象语法树,对于语法树结点需要进行不同的处理:类型检查,代码优化等

  • 将操作和结点类耦合带来的问题:将所有这些操作分散到各种结点类中会导致整个系统难以理解、难以维护和修改;此外,增加新的操作通常需要重新编译所有这些类
  • 解决方法:将每一个类中相关的操作包装在一个独立的对象(称为一个 Visitor)中,并在遍历抽象语法树时将此对象传递给当前访问的元素
    • 所有抽象语法树的访问者有一个抽象的父类 NodeVisitor。NodeVisitor 必须为每一个结点类定义一个操作 使用 Visitor 模式,必须定义两个类层次:
  • 一个对应于接受操作的元素(Node 层次)
  • 另一个对应于定义对元素的操作的访问者(NodeVisitor 层次)。给访问者类层次增加一个新的子类即可创建一个新的操作。

适用性

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。

结构

  • Visitor:接口。为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作,该操作的名字和特征标识了发送 Visit 请求给该访问者的类。

    • 访问者就可以通过该元素的特定接口直接访问它。
  • ConcreteVisitor:实现每个由 Visitor 声明的操作。每种具体的 Visitor 实现一种特定操作

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

  • ObjectStructure(例如 List,Map):能枚举它的元素,可以提供一个高层的接口以允许该 Visitor 访问它的元素

  • 一个使用 Visitor 模式的客户必须创建一个 ConcreteVisitor 对象,然后遍历该对象结构,并用该访问者访问每一个元素。

  • 当一个元素被访问时,它调用对应于它的类的 Visitor 操作。

效果

  • 访问者模式使得易于增加新的操作:仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一个类。

  • 访问者集中相关的操作而分离无关的操作:相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中;无关行为却被分别放在各自的访问者子类中

  • 增加新的 ConcreteElement 类很困难:每添加一个新的 ConcreteElement 都要在 Vistor 中添加一个新的抽象操作,并在每一个 ConcretVisitor 类中实现相应的操作。

  • 和迭代器的异同:

    • 迭代器(参见 Iterator(5.4))可以通过调用结点对象的特定操作来遍历整个对象结构,这就意味着该迭代器能够访问的所有元素都有一个共同的父类 Item。
    • 访问者没有这种限制。它可以访问不具有相同父类的对象。
1
2
3
4
5
class Visitor {
public 
	void VisitMytype(Mytype*);
	void VisitYourtype(yourType*);
}
  • 累积状态: 当访问者访问对象结构中的每一个元素时,后者可能会累积状态。如果没有访问者,这一状态将作为额外的参数传递给进行遍历的操作

  • 破坏封装:该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。

实现

Visitor 的关键

双分派:得到执行的操作不仅取决于 Visitor 的类型还取决于它访问的 Element 的类型。

  • 将操作安放在一个 Visitor 中,并使用 Accept 在运行时进行绑定。
  • 扩展 Element 接口就等于定义一个新的 Visitor 子类而不是多个新的 Element 子类。

代码实现

C++中,Visitor 类可以这样定义:

1
2
3
4
5
6
7
8
class Visitor {
public:
  virtual void VisitE1ementA(ElementA *);
  virtual void VisitE1ementB(ElementB *);
  // ...
protected:
  Visitor();
}

每个 ConcreteElement 类实现一个 Accept 操作,这个操作调用访问者中相应于本 ConcreteElement 类的 Visit 操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Element {
public:
  virtual ~Element() = 0;
  virtual void Accept(Visitor &) = 0;

protected:
  Element();
};
class ElementA : public Element {
public:
  ElementA();
  virtual void Accept(Visitor &v);
};
class ElementB : public Element {
public:
  ElementB();
  virtual void Accept(Visitor &v) { v.VisitElementB(this); }
};

CompositeElement 则这样实现 Accept

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class CompositeElement : public Element {
public:
  virtual void Accept(Visitor &);

private:
  List<Element *> *_children;
};
void CompositeElement::Accept(Visitor &v) {
  ListIterator<Element *> i(_children);
  for (i.First(); !i.IsDone(); i.Next())
    i.Current_Item()->Accept(v);
  v.VisitCompositeElement(this);
}