例如对于男人和女人,都可以去美发,但具体实现是不同。如果分别对男人和女人做头发,那么成本又高又浪费资源,不如开家美发店,同时提供男人和女人的美发服务。
表示一个作用于[1]某对象结构中各元素的操作,使程序员可以在[2]不改变各元素的类的前提下定义作用于这些元素的新操作。
-
作用于对象结构中的各元素
-
不同的元素结点,但都源于同一个接口(例如男女都是人), —— 结点类Node。
-
可以从中抽象出相同的操作,封装为访问者类Visitor。
-
需要遍历所有结点。
-
十分抵触对象结构本身的扩展。
-
-
不改变各元素的类
-
结点类中有接受访问者对象的方法Accept。
-
访问者类中有某种方法对所有具体结点的实现VisitConcreteNode'X'。
-
-
谁负责遍历结点?
-
三个地方: 对象结构中,访问者中,一个独立的迭代器对象中
-
通常由对象结构负责迭代,或是让Accept操作遍历该元素的各子构件并对每一个递归调用Accept。
-
-
单分派和双分派问题
-
C++只能实现accept的单分派:取决于请求的名和接收者的类型。
-
accept的双分派是:执行的操作取决于请求的种类和两个接收者的类型(Visitor和Node的类型)。在支持多分派的语言中,该方法的必需性就变小了
-
要素
每个Node类中有一个 accept( Visitor* ), 这里的Node是整个对象结构中的类对象, 注意这里并不要求Node继承于同一父类。
Visitor类中有(多个) visitConcreteNode'X'( ConcreteNode'X'* ); 名字最好取成操作的名称。
ConcreteVisitor实现对应于每个Node的操作实现。为操作(因为新这里实现的算法可能只是整个算法的片段)提供上下文并存储其局部状态。
客户使用具体的Visitor对象和对象结构中的具体结点元素Node
协作
- 创建ConcreteVisitor对象,遍历对象结构,并用ConcreteVisitor访问每一个结点。
- 当一个元素被访问时,调用对应于它的类的Visitor操作。如果必要(需要改变结点状态),该结点将自身作为参数传递给该Visitor。
- 易于添加新的操作 —— 增加一个新的Visitor即可在一个对象上定义一个新的操作。这使得增加依赖于复杂对象结构的构件的操作变简单了。
- ConcreteVisitor集中于单一职责,分离了无关操作。
- 这里的结点对象不要求继承同一父类,只要在Visitor中设置了对应的接口即可遍历。
- 增加新的结点会很困难 —— 需要修改所有的访问者类家族。
- Visitor在遍历所有结点时可能会累积状态(当状态作为参数提供给visitConcreteNode接口时)。
- 用访问者实现对结点功能的更新时,或visitConcreteNode接口的功能足够强,以至于可以履行结点职责时,迫使Visitor提供被访问结点的内部状态操作,从而破坏封装性。
适用于数据结构/对象结构稳定的系统,其将数据结构和作用于数据结构上的操作分离开,使操作集合:
- 对象结构中的类对象数量很少变化,但需要在此结构上定义新的操作;
- 该对象结构被需要应用共享时,用Visitor来实现对不同应用的特化操作;
- 一个对象结构中有许多类对象,且它们有不同的接口,想对这些对象实施一些依赖于某些具体类的操作(将相关操作封装为一个操作对象,从而和不相关的操作隔离)