一、概述
DP一书对Visitor模式的定义:表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这个元素的新的操作。
在软件构建的过程中,由于需求的变化我们常常需要扩展已有的类层次结构(如:增加新的行为)。如果直接在基类中进行修改,将会给子类带来繁重的变更负担,甚至破坏原有的设计。而且有些情况下,我们扩展生成的行为与现有对象模型是不一致的或无法访问现有的程序代码。
如何在不改变一个层次结构中的类的情况下,在运行时根据需求透明地为一个层次结构定义一个新的操作呢?
二、分析问题
考虑下面的问题:不同类型的形状(Shape)有各自的特点,因此子类(Rectangle、Circle)中的实现方法也各不相同。这时如果子类扩展了父类,我们如何使用这些功能呢?
我们可能会有如下实现:
2
运行结果:The Rectangle's Area is:15!
The Circle's Area is 28.26!
Let's Draw a circle,the circle's radius is 3!
一方面,我们希望接口统一的前提下使用子类的功能。另一方面,我们又不得不进行类型判断,并且常常需要强制类型转换。这实际上违背了面向接口的设计原则,因为我们必须针对某种实现执行特定的代码。此外,如果由于需求的变更Shape类需要增加新的方法,那么其子类将不得不随之更改。
三、Visitor模式的UML类图
由图可见,Visitor模式适用于类结构稳定的系统 (类或子类一旦确定,就不会经常修改或增加新的类)。因为访问者(Visitor)依赖于类结构中的所有子类,一旦子类发生变化,访问者及相应的子类都要随之变化。
但是仅仅这个理由是不够的,正如Ralph Johnson(DP的作者之一)所说:“大多时候你不需要Visitor,但是一旦你需要Visitor,那就是真的需要Visitor了”。一般来说,当你有很多运行在不同对象结构中的算法,并且没有其他的解决方案比Visitor更简单或简洁的时候,你就需要Visitor。
通过使用双重分派(double-dispatch),Visitor可以很容易的与不同的类进行交互。这意味着类集合中的每一个类都接受一个Visitor作为参数(通过一个“接受”方法:Accept(Visitor visitor))的方法,然后回调这个Visitor,把自己传入相应的访问方法中。
因为传入Visitor的Visitor(….)方法的参数是一个特殊类型的实例,所以Visitor可以调用这个实例上的特定方法,而不需要做类型转换。这就使得Visitor可以访问统一继承结构或不同继承结构中的类。
通过访问者为对象增加操作与在对象内部直接增加操作是不同的,在某些情况下,为了使访问者访问对象的内部变量,对象将不得不暴露一些自己的操作和状态。另外,由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在相应的元素中,这都破坏了对象的封装性。
四、具体的例子——汽车维修工程师检查汽车
(1)类图
(2)实现代码
2
3
---------------------------------------
Check Left_Front Wheel Complete!
Check Right_Front Wheel Complete!
Check Left_Bank Wheel Complete!
Check Right_Bank Wheel Complete!
[参考文献]Refactoring to Patterns