0%

访问者模式

访问者模式

访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。

visitor.png

模式动机

假如某个软件团队开发了一款能够使用巨型图像中地理信息的应用程序。 图像中的每个节点既能代表复杂实体 (例如一座城市), 也能代表更精细的对象 (例如工业区和旅游景点等)。 如果节点代表的真实对象之间存在公路, 那么这些节点就会相互连接。 在程序内部, 每个节点的类型都由其所属的类来表示, 每个特定的节点则是一个对象。

一段时间后, 软件团队接到了实现将图像导出到 XML 文件中的任务,如下图所示。

problem1.png

图8.2.1 将图像导出为 XML。

这些工作最初看上去非常简单。 该团队计划为每个节点类添加导出函数, 然后递归执行图像中每个节点的导出函数。 解决方案简单且优雅: 使用多态机制可以让导出方法的调用代码不会和具体的节点类相耦合。

但团队的系统架构师拒绝批准对已有节点类进行修改。 他认为这些代码已经是产品了, 不想冒险对其进行修改, 因为修改可能会引入潜在的缺陷。

problem2-zh.png

所有节点的类中都必须添加导出至 XML 文件的方法, 但如果在修改代码的过程中引入了任何缺陷, 那么整个程序都会面临风险。

此外, 他还质疑在节点类中包含导出 XML 文件的代码是否有意义。 这些类的主要工作是处理地理数据。 导出 XML 文件的代码放在这里并不合适。

还有另一个原因, 那就是在此项任务完成后, 营销部门很有可能会要求程序提供导出其他类型文件的功能, 或者提出其他奇怪的要求。 这样你很可能会被迫再次修改这些重要但脆弱的类。

该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:

  1. 新功能会不会与现有功能出现兼容性问题?
  2. 以后会不会再需要添加?
  3. 如果类不允许修改代码怎么办?

面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦。

总结来说,在软件设计中,下面的情况可以使用访问者模式。

  • 在实际使用时,对同一集合对象的操作并不是唯一的,对相同的元素对象可能存在多种不同的操作方式。而且这些操作方式并不稳定,可能还需要增加新的操作,以满足新的业务需求。此时,访问者模式就是一个值得考虑的解决方案。
  • 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。

简单来说,访问者模式就是一种分离****对象数据结构行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。

模式定义

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。

模式结构

visitor-diagram.png

访问者模式包含如下角色:

  • Vistor: 抽象访问者。定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的。
  • ConcreteVisitor: 具体访问者。给出对每一个元素类访问时所产生的具体行为。
  • Element: 抽象元素。定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • ConcreteElement: 具体元素。提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • ObjectStructure: 对象结构。定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

模式时序图

visitor-sequence.png

模式抽象代码分析

抽象访问者类:

img

具体访问者类:

img

抽象元素:

img

具体元素:

img

对象结构:

img

模式分析

  • 访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。
  • 访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。
  • 相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性

模式实例

在本示例中,希望将一组几何形状导出到 XML文件。 存在的问题是,不能直接更改形状的代码,或者尽量少地修改形状代码。为此,使用访问者模式建立了一个基类,允许将任何行为添加到形状层次结构中,而无需更改这些类的现有代码。

example.png

Shape.java

img

Dot.java

img

Circle.java

img

Rectangle.java

img

CompoundShape.java

img

Visitor.java

img

Visitor.java

img

Client.java

img

模式优点

  • 使得增加新的访问操作变得很容易。
  • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。
  • 可以跨过类的等级结构访问属于不同的等级结构的元素类。
  • 让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作。

模式缺点

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了开闭原则的要求
  • 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

模式适用环境

  • 一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

模式应用

  1. Java类库实例

    javax.lang.model.element.AnnotationValue和AnnotationValueVisitor

    javax.lang.model.element.Element和ElementVisitor

    javax.lang.model.type.TypeMirror和TypeVisitor

    java.nio.file.FileVisitor和SimpleFileVisitor

    javax.faces.component.visit.VisitContext和VisitCallback

  2. 在一些编译器的设计中运用了访问者模式,程序代码是被访问的对象,它包括变量定义、变量赋值、逻辑运算、算术运算等语句,编译器需要对代码进行分析,如检查变量是否定义、变量是否赋值、算术运算是否合法等,可以将不同的操作封装在不同的类中,如检查变量定义的类、检查变量赋值的类、检查算术运算是否合法的类,这些类就是具体访问者,可以访问程序代码中不同类型的语句。在编译过程中除了代码分析外,还包含代码优化、空间分配和代码生成等部分,也可以将每一个不同编译阶段的操作封装到了跟该阶段有关的一个访问者类中。

  3. 在常用的Java XML处理技术DOM4J中,可以通过访问者模式的方式来读取并解析XML文档,VisitorSupport是DOM4J提供的Visitor接口的默认适配器,具体访问者只需继承VisitorSupport类即可。

模式扩展

\1. 与其他模式联用

  • 由于访问者模式需要对对象结构进行操作,而对象结构本身是一个元素对象的集合,因此访问者模式经常需要与迭代器模式联用,在对象结构中使用迭代器来遍历元素对象。
  • 在访问者模式中,元素对象可能存在容器对象和叶子对象,因此可以结合组合模式来进行设计。

\2. 访问者模式以一种倾斜的方式支持开闭原则,增加新的访问者方便,但是增加新的元素很困难。

-------------本文结束感谢您的阅读-------------
老板你好,讨口饭吃