6.2访问者模式(5.11)

6.1 Java模拟 双分派(Double Dispatch)

上节[6.1 Java模拟双分派]重点关注源代码的变化。本节讨论实验结果的意义——访问者(Visitor)模式使用双分派技术,能够为被访问者添加新的操作。【 注:Java模拟双分派,解决了Visitor模式的结构和实现方面的问题。本节讲大道理】

6.2.1表达式问题 

通常情况下,Shape所需的一些方法draw()和area()由Shape自己封装,而Shape的子类型Circle和Square分别提供实现代码。测试代码为:

Shape s1 = new Circle(4.0);  // s1 = ( Shape) God.create("shape ");
Shape s2 = new Square(4.0);
              s1. draw() ; s1. area();
              s2. draw() ; s2. area();

调用两个对象的两个方法有4种组合,上一节中,用两个Visitor对象通过Visitor.vist(Shape)的格式调用了4种组合,如new DrawVisitor ().visit(s1)等价于s1. draw()的调用。

为什么Shape所需的一些方法,不由Shape自己封装而是由一个具体访问者针对一个方法进行封装?其原因在于:如果Shape发布出去之后,为Shape添加新的抽象方法如填充/fill(),将会导致Shape的整个类层次需要修改(违反OCP),而且其他程序员编写的Shape的子类型全面中断——新版本的Shape与旧版本不兼容。

如果使用模拟双分派,则可以方便地编写FillVisitor,如同/相当于给Shape添加新的操作;

但是,为Shape添加新的子类型如三角形S3变得困难,因为受到整个Visitor类层次的拖累,Visitor中需要添加新的接口foo(S3 x)。

这一现象被称为著名的表达式问题或扩展性问题

★表达式问题…的目标是,一个数据类型,人们可以为它添加新的分支(子类型)和新的操作,无需重新编译现有代码,同时保留静态类型安全(例如,没有数据类型转换)。

表达式问题是一个两难的选择。也就是说,Java语言的抽象类型Shape,在需求变化时,可以方便地增添Shape的更多的子类型,但是不能够添加新的接口;在使用访问者Visitor时则相反,可以方便地(如同/相当于)给Shape添加新的操作,但是Shape不能够添加新的子类型。所以使用访问者,Shape的所有子类型需要事先编写完整,如果有三角形、椭圆等必须全部编写出来,因为后期难以扩展。

由此,说明了访问者模式的目标,是为被访问者添加新的操作。在设计实践中,程序员其实难以决定是否使用这一模式。因为他必须在类型的扩展性和接口的扩展性之间做出事先的选择。

★访问者模式,支持被访问者拥有新接口。

Shape有方法draw()和area(),这些操作的实现分散到Shape的各个子类型中,这是否导致GoF所说的“整个系统难以理解、难以维护和修改”呢?事实上,按照单一职责原则,设计者需要考虑的问题是draw()和area()是否适合放在一个类型中,而非操作的实现分散到Shape的各个子类型中。如果设计者执意要比较Shape子类型的area()的代码的不同,将所有的area()的实现放在一个AreaVisitor中,事实上是一种非常奇葩的设计。

6.2.2 对象结构

面向对象的设计者,不会考虑将Shape类层次与它的方法draw()和area()分离。因此访问者模式没有“将数据操作和数据结构分离”的动机,本质上访问者模式中没有“对象结构”的位置。GoF在意图中称为访问者模式“表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作”,不得要领。“对象结构”并非访问者模式的要素。

假设选择接口的扩展性优先,即采用访问者模式,被访问者Shape通常退变为单纯的“数据容器类”,而访问者类层次为Shape提供行为。可以用数学的等号表示为:

DrawVisitor. forCircle(Circle) = Circle. draw()

即Circle. draw()现在由DrawVisitor中的forCircle(Circle)封装代码,而作为参数的(Shape的子类型如)Circle,为具体访问者DrawVisitor提供数据。

被访问者Shape,当它的行为由Visitor接管,则退变为单纯的“数据容器类”;更进一步,如果Shape不需要数据,它就演化成一个单纯地带参数的命令,因此被访问者不再是与应用相关的常规概念,而是访问者模式中需要的概念——接受访问的对象,因此可以命名为Visitable。

 

public interface Visitable {// Shape

    public <R>  R accept(Visitor<R> v);

}

一旦抽象出更一般性的概念,就拥有更广泛地应用场景。虽然就本质而言,在讨论访问者模式时,不关心访问者模式的“对象结构”。但就应用而言,接口Visitable能够接受任意的类型作为自己的子类型;当存在一系列具体被访问者时,通常会将它们组织成某种对象结构。

例如对轿车/Car进行保养等各种操作,汽车以及汽车的部分如轮胎/Wheel、引擎/Engine、车体/Body可以通过组合模式组织成整体-部分结构,以ICarElement为父类型。它的部件子类型构造成一个对象结构即Car。

例如对购物车中所有商品进行价格计算和安全检查(食品安全,书籍内容),购物车中诸多商品以Product为父类型,诸多商品可以被组织成ArrayList<Product>;

 

返回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值