访问者模式(Vistor Pattern)

版权声明:本文为博主学习笔记, 注明来源情况下随意转载 https://blog.csdn.net/lengxiao1993/article/details/73603929
  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. 参考文章: 设计模式(20)-Visitor Pattern

设计模式用前须知

大部分程序员编写的程序可以分为三类(应用程序、工具包 、框架),使用设计模式的目的是提高代码的可复用性和可扩展性(灵活性), 但是设计模式在这三类软件中所发挥的效果是不一样的。

很多有经验的程序员会得出“使用了设计模式,反而降低了代码的可读性,增加了复杂度”的结论, 并把这种问题总结为过度设计。 这种总结其实有失偏颇, 因为设计模式所提供的灵活性本身就是有代价的。 很多程序员所编写的商业应用程序, 其问题领域其实相对固定,对代码的灵活性和重用性要求并不高(相对于与工具包和框架而言), 所以应用程序的编写者往往在不了解设计模式的情况下,也可以很好地解决一些灵活性需求和扩展性需求。

具体来说使用设计模式的必要性的程度是逐级递增的:应用程序(Application) < 工具包/类库(ToolKit/Library) < 框架(Framework)

访问者模式(Vistor Pattern)

  • 设计意图

    • GoF: 将要施加于一个某种数据结构各个元素的操作接口化表示, 使得你可以在不用修改数据结构中的元素类的情况下, 添加一些新的操作。
    • 解释: GoF 的描述比较抽象, 上述的某种数据结构是指包含了多种数据元素的结构, 例如 Tree,List,Set 。 对这一类集合数据结构中的元素进行操作, 听上去就像写一个 for 循环遍历而已, 调用不同的 visit(element) 方法一样简单。 但是 Vistor 模式想解决的是, 当集合中的元素类别都不相同且同一次遍历对不同类别的操作也不同时, 应当如何写出容易扩展的代码结构。
  • GOF举例:

    • 考虑一个 会把程序代码解析成一个抽象语法树的编译器 Compilier. 它会需要在解析出的抽象语法树上进行一些操作以完成“静态语义分析”(static semantic analysis), 例如检查所有定义的变量。它还需要生成二进制代码, 或字节码, 所以它可能要定义一些类型检查 , 代码优化, 信息流分析(flow analysis), 检查变量使用前是否赋值的等等操作。 此外, 我们还可能使用解析出的语法树来进行代码格式化显示(高亮, 排版缩进), 代码植入,性能分析等等操作。
    • 上述提到的大部分操作需要把区别对待语法树上代表赋值语句的结点(assignment statement node)和算术操作结点( arithmetic statement node)和变量结点( variable node )。 因此, 语法树上的结点会根据其类型单独定义各自的类。例如 变量结点(VariableRefNode) , 赋值结点(AssignmentNode)。 这一系列的节点类如何定义依赖于要编译的语言, 对于给定一个语言, 这些结点类不会有太大的改动。

    这里写图片描述

    • 上图展现了 Node 类层级。 这样设计的弊端在于它把所有需要在各个结点上进行的同类操作分散到各式各样的结点类中, 这会导致系统难以理解, 维护和修改。把类型检查和信息流分析等不同的操作混合在一起是很容易让阅读代码的人困惑的 。 而且, 如果要添加一个新的操作, 意味着要向每一个类中添加该操作的实现并且重新编译所有的结点类 。 如果可以单独添加一个操作而不影响各个不同的结点类, 很明显会是更好的设计。
  • 解决方案

    • 访问者模式(Vistor Pattern)可以通过将相关的操作打包到单独的 vistor 类中, 然后将它传递给我们希望访问的抽象语法树的元素来实现我们的目标。 当一个元素 ” 接收 (accept)” 一个 visitor 时, 它会将自己的类别信息及自身所包含的内容作为参数传递给 vistor, 继而 vistor 就可以根据该元素的类别, 来进行针对该类的操作 。
    • 一个编译器 compilier 如果使用之前的设计方式,加入要对一段代码进行类型检查的操作, 就需要在解析出的抽象语法树上的每一个节点上调用 TypeCheck 方法。 而语法树上的每一种节点类都需要实现 TypeCheck 方法。
    • 如果 compilier 使用 vistor 来实现类型检查操作, 那么就需要创建一个 TypeCheckingVistor 对象, 然后对整个语法树调用 accept(vistitor) 方法,将所创建的 vistor 对象作为参数传递进去。 每一个节点都需要实现 accept 接口来接受 vistor, 并回调 vistor 访问自己 。

    这里写图片描述

    这里写图片描述

  • 如以上两个图例所示, 使用访问者模式, 需要定义两个类层级结构: 一个用于定义元素类层级(Node 层级), 另一定义 Vistor 层级 (NodeVistor 层级)。 当需要创建一个新的操作时, 只要编译器所接受的语法没有改变, 我们仅仅需要向 Visitor 层级中添加 一个子类就可以完成。

应用场景

在下列情况使用访问者模式:

  • 一个数据结构中包含了多个类的对象,且这些类的接口都不相同。 你希望对这些对象根据他们的类进行不同的操作。

  • 需要被访问的数据结构中所定义的类几乎不怎么会改变, 但是你有可能经常需要针对该结构定义新的操作。

访问者模式结构

这里写图片描述

  • 图例说明
    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的实心三角箭头且箭头末尾没有圆圈的, 代表着单一的引用关系, 但是被引用的对象也有可能被其他对象引用。
    • 图片中的实心三角箭头且箭头末尾有圆圈的, 代表着一对多的引用关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系

访问者模式总结

访问者模式的有两个关键点:

  • 需要访问的数据结构中包含的元素并不是同一类型的, 而是多个不同类型, 相同目的的访问需要根据元素类型进行不同的操作。
  • 被访问的数据结构中所包含的元素类别几乎不怎么改变, 因为使用访问模式以后, 添加一个新的元素类会需要对所有的 Vistor 子类进行修改, 定义新的访问方法。

猜你喜欢

转载自blog.csdn.net/lengxiao1993/article/details/73603929
今日推荐