1. 人人编程首页
  2. 架构设计

访问者设计模式

意图

  • 表示要对对象结构的元素执行的操作。Visitor 允许您定义一个新的操作,而无需更改它所操作的元素的类。
  • 恢复丢失类型信息的经典技术。
  • 根据两个对象的类型做正确的事。
  • 双重派送

问题

需要对异构聚合结构中的节点对象执行许多不同且不相关的操作。您希望避免使用这些操作“污染”节点类。而且,您不希望在执行所需操作之前查询每个节点的类型并将指针转换为正确的类型。

讨论

访问者的主要目的是抽象可应用于“元素”对象的聚合层次结构的功能。该方法鼓励设计轻量级元素类 – 因为处理功能已从其职责列表中删除。通过创建新的访问者子类,可以轻松地将新功能添加到原始继承层次结构中。

访客实现“双重调度”。OO 消息通常表现为“单次调度”——执行的操作取决于:请求的名称和接收者的类型。在“双重调度”中,执行的操作取决于:请求的名称,以及两个接收者的类型(访问者的类型和它访问的元素的类型)。

实施过程如下。visit()创建一个访问者类层次结构,该层次结构在抽象基类中为聚合节点层次结构中的每个具体派生类定义一个纯虚方法。每个visit()方法都接受一个参数 – 指向原始 Element 派生类的指针或引用。

要支持的每个操作都使用访问者层次结构的具体派生类建模。visit()现在,通过将原始实现中的“类型查询和转换”代码分配给适当的重载方法,在每个派生子类中定义了在访问者基类中声明的方法visit()

将单个纯虚accept()方法添加到 Element 层次结构的基类。 accept()被定义为接收单个参数 – 指向访问者层次结构的抽象基类的指针或引用。

Element 层次结构的每个具体派生类 accept()通过简单地调用visit()它所传递的访问者层次结构的具体派生实例上的方法来实现该方法,并将其“this”指针作为唯一参数传递。

“元素”和“访客”的所有内容现在都已设置完毕。当客户端需要执行某个操作时,他会创建一个访问者对象的实例,调用accept()每个元素对象的方法,并传递访问者对象。

accept()方法使控制流找到正确的 Element 子类。然后,当visit()调用该方法时,控制流被引导到正确的访问者子类。accept()分派加visit() 分派等于双重分派。

访问者模式使添加新操作(或实用程序)变得容易——只需添加一个新的访问者派生类。但是,如果聚合节点层次结构中的子类不稳定,则保持访问者子类同步需要大量的努力。

对访问者模式的一个公认反对意见是,它代表了对功能分解的回归——将算法与数据结构分开。虽然这是一个合理的解释,但也许更好的观点/理由是将非传统行为提升为完全对象状态的目标。

结构

元素层次结构使用“通用方法适配器”进行检测。每个 Element 派生类中的实现accept()总是相同的。但是 – 它不能移动到 Element 基类并由所有派生类继承,因为 thisElement 类中的引用始终映射到基类型 Element。

访问者设计模式

firstDispatch()对抽象对象调用多态方法时First,该对象的具体类型被“恢复”。当secondDispatch() 对抽象对象调用多态方法时Second,它的具体类型是“恢复的”。现在可以使用适用于这对类型的应用程序功能。

访问者设计模式

例子

访问者模式表示要在对象结构的元素上执行的操作,而不更改它所操作的类。这种模式可以在出租车公司的运营中观察到。当有人打电话给出租车公司(接待访客)时,公司会派出租车给客户。进入出租车后,客户或访客不再控制他或她自己的交通工具,出租车(司机)是。

访问者设计模式

检查清单

  1. 确认当前层次结构(称为元素层次结构)将相当稳定,并且这些类的公共接口足以满足访问者类所需的访问权限。如果不满足这些条件,则访问者模式不是很好的匹配。
  2. visit(ElementXxx) 为每个 Element 派生类型创建一个带有方法的访问者基类。
  3. accept(Visitor)方法添加到元素层次结构。每个 Element 派生类中的实现总是相同的 – accept( Visitor v ) { v.visit( this ); }。由于循环依赖,Element 和 Visitor 类的声明需要交错。
  4. Element 层次结构仅与 Visitor 基类耦合,但 Visitor 层次结构与每个 Element 派生类耦合。如果Element层级稳定性低,Visitor层级稳定性高;考虑交换两个层次结构的“角色”。
  5. 为要在 Element 对象上执行的每个“操作”创建一个访问者派生类。 visit()实现将依赖于元素的公共接口。
  6. 客户端创建 Visitor 对象并通过调用将每个对象传递给 Element 对象accept()

经验法则

  • Interpreter 的抽象语法树是 Composite(因此 Iterator 和 Visitor 也适用)。
  • Iterator 可以遍历 Composite。访问者可以对组合应用操作。
  • 访问者模式就像一个更强大的命令模式,因为访问者可以启动适合它遇到的对象类型的任何内容。
  • 访问者模式是无需借助动态转换即可恢复丢失类型信息的经典技术。

注意事项

JavaPro 2000 年 11 月号有一篇由 James Cooper(GoF 的 Java 伴侣的作者)撰写的关于访问者设计模式的文章。他建议它“在我们的面向对象模型上扭转局面,并创建一个外部类来处理其他类中的数据……虽然这可能看起来不干净……但这样做有充分的理由。”

他的主要例子。假设您有一个员工-工程师-老板的层次结构。他们都享受正常的假期累积政策,但是,老板也参加“奖金”假期计划。因此,Boss 类的接口与 Engineer 类的接口不同。我们不能多态地遍历一个类似于 Composite 的组织并计算该组织剩余假期的总和。“当有多个具有不同接口的类并且我们想要封装如何从这些类中获取数据时,Visitor 变得更加有用。”

他对访客的好处包括:

  • 将函数添加到您没有源或无法更改源的类库
  • 从不相关类的不同集合中获取数据,并使用它将全局计算的结果呈现给用户程序
  • 将相关操作收集到单个类中,而不是强迫您更改或派生类来添加这些操作
  • 与复合模式协作

访问者不适合“访问”类不稳定的情况。每次添加新的复合层次派生类时,都必须修改每个访问者派生类。

本文来自转载,原文链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注