# 16.7 访问器模式
接下来,让我们思考如何将具有完全不同目标的一个设计模式应用到垃圾归类系统。
对这个模式,我们不再关心在系统中加入新型`Trash`时的优化。事实上,这个模式使新型`Trash`的添加显得更加复杂。假定我们有一个基本类结构,它是固定不变的;它或许来自另一个开发者或公司,我们无权对那个结构进行任何修改。然而,我们又希望在那个结构里加入新的多态性方法。这意味着我们一般必须在基类的接口里添加某些东西。因此,我们目前面临的困境是一方面需要向基类添加方法,另一方面又不能改动基类。怎样解决这个问题呢?
“访问器”(`Visitor`)模式使我们能扩展基本类型的接口,方法是创建类型为`Visitor`的一个独立的类结构,对以后需对基本类型采取的操作进行虚拟。基本类型的任务就是简单地“接收”访问器,然后调用访问器的动态绑定方法。看起来就象下面这样:
![](https://box.kancloud.cn/66baf8e8ffd4ee1db267ba488f76c20b_445x595.gif)
现在,假如v是一个指向`Aluminum`(铝制品)的`Visitable`引用,那么下述代码:
```
PriceVisitor pv = new PriceVisitor();
v.accept(pv);
```
会造成两个多态性方法调用:第一个会选择`accept()`的`Aluminum`版本;第二个则在`accept()`里——用基类`Visitor`引用`v`动态调用`visit()`的特定版本时。
这种配置意味着可采取`Visitor`的新子类的形式将新的功能添加到系统里,没必要接触`Trash`结构。这就是“访问器”模式最主要的优点:可为一个类结构添加新的多态性功能,同时不必改动结构——只要安装好了`accept()`方法。注意这个优点在这儿是有用的,但并不一定是我们在任何情况下的首选方案。所以在最开始的时候,就要判断这到底是不是自己需要的方案。
现在注意一件没有做成的事情:访问器方案防止了从主控`Trash`序列向单独类型序列的归类。所以我们可将所有东西都留在单主控序列中,只需用适当的访问器通过那个序列传递,即可达到希望的目标。尽管这似乎并非访问器模式的本意,但确实让我们达到了很希望达到的一个目标(避免使用RTTI)。
访问器模式中的双生分发负责同时判断`Trash`以及`Visitor`的类型。在下面的例子中,大家可看到`Visitor`的两种实现方式:`PriceVisitor`用于判断总计及价格,而`WeightVisitor`用于跟踪重量。
可以看到,所有这些都是用回收程序一个新的、改进过的版本实现的。而且和`DoubleDispatch.java`一样,`Trash`类被保持孤立,并创建一个新接口来添加`accept()`方法:
```
//: Visitable.java
// An interface to add visitor functionality to
// the Trash hierarchy without modifying the
// base class.
package c16.trashvisitor;
import c16.trash.*;
interface Visitable {
// The new method:
void accept(Visitor v);
} ///:~
```
`Aluminum`,`Paper`,`Glass`以及`Cardboard`的子类型实现了`accept()`方法:
```
//: VAluminum.java
// Aluminum for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
public class VAluminum extends Aluminum
implements Visitable {
public VAluminum(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VPaper.java
// Paper for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
public class VPaper extends Paper
implements Visitable {
public VPaper(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VGlass.java
// Glass for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
public class VGlass extends Glass
implements Visitable {
public VGlass(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VCardboard.java
// Cardboard for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
public class VCardboard extends Cardboard
implements Visitable {
public VCardboard(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
```
由于`Visitor`基类没有什么需要实在的东西,可将其创建成一个接口:
```
//: Visitor.java
// The base interface for visitors
package c16.trashvisitor;
import c16.trash.*;
interface Visitor {
void visit(VAluminum a);
void visit(VPaper p);
void visit(VGlass g);
void visit(VCardboard c);
} ///:~
c16.TrashVisitor.VGlass:54
c16.TrashVisitor.VPaper:22
c16.TrashVisitor.VPaper:11
c16.TrashVisitor.VGlass:17
c16.TrashVisitor.VAluminum:89
c16.TrashVisitor.VPaper:88
c16.TrashVisitor.VAluminum:76
c16.TrashVisitor.VCardboard:96
c16.TrashVisitor.VAluminum:25
c16.TrashVisitor.VAluminum:34
c16.TrashVisitor.VGlass:11
c16.TrashVisitor.VGlass:68
c16.TrashVisitor.VGlass:43
c16.TrashVisitor.VAluminum:27
c16.TrashVisitor.VCardboard:44
c16.TrashVisitor.VAluminum:18
c16.TrashVisitor.VPaper:91
c16.TrashVisitor.VGlass:63
c16.TrashVisitor.VGlass:50
c16.TrashVisitor.VGlass:80
c16.TrashVisitor.VAluminum:81
c16.TrashVisitor.VCardboard:12
c16.TrashVisitor.VGlass:12
c16.TrashVisitor.VGlass:54
c16.TrashVisitor.VAluminum:36
c16.TrashVisitor.VAluminum:93
c16.TrashVisitor.VGlass:93
c16.TrashVisitor.VPaper:80
c16.TrashVisitor.VGlass:36
c16.TrashVisitor.VGlass:12
c16.TrashVisitor.VGlass:60
c16.TrashVisitor.VPaper:66
c16.TrashVisitor.VAluminum:36
c16.TrashVisitor.VCardboard:22
```
程序剩余的部分将创建特定的`Visitor`类型,并通过一个`Trash`对象列表发送它们:
```
//: TrashVisitor.java
// The "visitor" pattern
package c16.trashvisitor;
import c16.trash.*;
import java.util.*;
// Specific group of algorithms packaged
// in each implementation of Visitor:
class PriceVisitor implements Visitor {
private double alSum; // Aluminum
private double pSum; // Paper
private double gSum; // Glass
private double cSum; // Cardboard
public void visit(VAluminum al) {
double v = al.weight() * al.value();
System.out.println(
"value of Aluminum= " + v);
alSum += v;
}
public void visit(VPaper p) {
double v = p.weight() * p.value();
System.out.println(
"value of Paper= " + v);
pSum += v;
}
public void visit(VGlass g) {
double v = g.weight() * g.value();
System.out.println(
"value of Glass= " + v);
gSum += v;
}
public void visit(VCardboard c) {
double v = c.weight() * c.value();
System.out.println(
"value of Cardboard = " + v);
cSum += v;
}
void total() {
System.out.println(
"Total Aluminum: $" + alSum + "\n" +
"Total Paper: $" + pSum + "\n" +
"Total Glass: $" + gSum + "\n" +
"Total Cardboard: $" + cSum);
}
}
class WeightVisitor implements Visitor {
private double alSum; // Aluminum
private double pSum; // Paper
private double gSum; // Glass
private double cSum; // Cardboard
public void visit(VAluminum al) {
alSum += al.weight();
System.out.println("weight of Aluminum = "
+ al.weight());
}
public void visit(VPaper p) {
pSum += p.weight();
System.out.println("weight of Paper = "
+ p.weight());
}
public void visit(VGlass g) {
gSum += g.weight();
System.out.println("weight of Glass = "
+ g.weight());
}
public void visit(VCardboard c) {
cSum += c.weight();
System.out.println("weight of Cardboard = "
+ c.weight());
}
void total() {
System.out.println("Total weight Aluminum:"
+ alSum);
System.out.println("Total weight Paper:"
+ pSum);
System.out.println("Total weight Glass:"
+ gSum);
System.out.println("Total weight Cardboard:"
+ cSum);
}
}
public class TrashVisitor {
public static void main(String[] args) {
Vector bin = new Vector();
// ParseTrash still works, without changes:
ParseTrash.fillBin("VTrash.dat", bin);
// You could even iterate through
// a list of visitors!
PriceVisitor pv = new PriceVisitor();
WeightVisitor wv = new WeightVisitor();
Enumeration it = bin.elements();
while(it.hasMoreElements()) {
Visitable v = (Visitable)it.nextElement();
v.accept(pv);
v.accept(wv);
}
pv.total();
wv.total();
}
} ///:~
```
注意`main()`的形状已再次发生了变化。现在只有一个垃圾(`Trash`)筒。两个`Visitor`对象被接收到序列中的每个元素内,它们会完成自己份内的工作。`Visitor`跟踪它们自己的内部数据,计算出总重和价格。
最好,将东西从序列中取出的时候,除了不可避免地向`Trash`转换以外,再没有运行期的类型验证。若在Java里实现了参数化类型,甚至那个转换操作也可以避免。
对比之前介绍过的双重分发方案,区分这两种方案的一个办法是:在双重分发方案中,每个子类创建时只会重载其中的一个重载方法,即`add()`。而在这里,每个重载的`visit()`方法都必须在`Visitor`的每个子类中进行重载。
(1) 更多的结合?
这里还有其他许多代码,`Trash`结构和`Visitor`结构之间存在着明显的“结合”(`Coupling`)关系。然而,在它们所代表的类集内部,也存在着高度的凝聚力:都只做一件事情(`Trash`描述垃圾或废品,而`Visitor`描述对垃圾采取什么行动)。作为一套优秀的设计模式,这无疑是个良好的开端。当然就目前的情况来说,只有在我们添加新的`Visitor`类型时才能体会到它的好处。但在添加新类型的`Trash`时,它却显得有些碍手碍脚。
类与类之间低度的结合与类内高度的凝聚无疑是一个重要的设计目标。但只要稍不留神,就可能妨碍我们得到一个本该更出色的设计。从表面看,有些类不可避免地相互间存在着一些“亲密”关系。这种关系通常是成对发生的,可以叫作“对联”(`Couplet`)——比如集合和迭代器(`Enumeration`)。前面的`Trash-Visitor`对似乎也是这样的一种“对联”。
- Java 编程思想
- 写在前面的话
- 引言
- 第1章 对象入门
- 1.1 抽象的进步
- 1.2 对象的接口
- 1.3 实现方案的隐藏
- 1.4 方案的重复使用
- 1.5 继承:重新使用接口
- 1.6 多态对象的互换使用
- 1.7 对象的创建和存在时间
- 1.8 异常控制:解决错误
- 1.9 多线程
- 1.10 永久性
- 1.11 Java和因特网
- 1.12 分析和设计
- 1.13 Java还是C++
- 第2章 一切都是对象
- 2.1 用引用操纵对象
- 2.2 所有对象都必须创建
- 2.3 绝对不要清除对象
- 2.4 新建数据类型:类
- 2.5 方法、参数和返回值
- 2.6 构建Java程序
- 2.7 我们的第一个Java程序
- 2.8 注释和嵌入文档
- 2.9 编码样式
- 2.10 总结
- 2.11 练习
- 第3章 控制程序流程
- 3.1 使用Java运算符
- 3.2 执行控制
- 3.3 总结
- 3.4 练习
- 第4章 初始化和清除
- 4.1 用构造器自动初始化
- 4.2 方法重载
- 4.3 清除:收尾和垃圾收集
- 4.4 成员初始化
- 4.5 数组初始化
- 4.6 总结
- 4.7 练习
- 第5章 隐藏实现过程
- 5.1 包:库单元
- 5.2 Java访问指示符
- 5.3 接口与实现
- 5.4 类访问
- 5.5 总结
- 5.6 练习
- 第6章 类复用
- 6.1 组合的语法
- 6.2 继承的语法
- 6.3 组合与继承的结合
- 6.4 到底选择组合还是继承
- 6.5 protected
- 6.6 累积开发
- 6.7 向上转换
- 6.8 final关键字
- 6.9 初始化和类装载
- 6.10 总结
- 6.11 练习
- 第7章 多态性
- 7.1 向上转换
- 7.2 深入理解
- 7.3 覆盖与重载
- 7.4 抽象类和方法
- 7.5 接口
- 7.6 内部类
- 7.7 构造器和多态性
- 7.8 通过继承进行设计
- 7.9 总结
- 7.10 练习
- 第8章 对象的容纳
- 8.1 数组
- 8.2 集合
- 8.3 枚举器(迭代器)
- 8.4 集合的类型
- 8.5 排序
- 8.6 通用集合库
- 8.7 新集合
- 8.8 总结
- 8.9 练习
- 第9章 异常差错控制
- 9.1 基本异常
- 9.2 异常的捕获
- 9.3 标准Java异常
- 9.4 创建自己的异常
- 9.5 异常的限制
- 9.6 用finally清除
- 9.7 构造器
- 9.8 异常匹配
- 9.9 总结
- 9.10 练习
- 第10章 Java IO系统
- 10.1 输入和输出
- 10.2 增添属性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File类
- 10.5 IO流的典型应用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 压缩
- 10.9 对象序列化
- 10.10 总结
- 10.11 练习
- 第11章 运行期类型识别
- 11.1 对RTTI的需要
- 11.2 RTTI语法
- 11.3 反射:运行期类信息
- 11.4 总结
- 11.5 练习
- 第12章 传递和返回对象
- 12.1 传递引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只读类
- 12.5 总结
- 12.6 练习
- 第13章 创建窗口和程序片
- 13.1 为何要用AWT?
- 13.2 基本程序片
- 13.3 制作按钮
- 13.4 捕获事件
- 13.5 文本字段
- 13.6 文本区域
- 13.7 标签
- 13.8 复选框
- 13.9 单选钮
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 视窗化应用
- 13.16 新型AWT
- 13.17 Java 1.1用户接口API
- 13.18 可视编程和Beans
- 13.19 Swing入门
- 13.20 总结
- 13.21 练习
- 第14章 多线程
- 14.1 反应灵敏的用户界面
- 14.2 共享有限的资源
- 14.3 堵塞
- 14.4 优先级
- 14.5 回顾runnable
- 14.6 总结
- 14.7 练习
- 第15章 网络编程
- 15.1 机器的标识
- 15.2 套接字
- 15.3 服务多个客户
- 15.4 数据报
- 15.5 一个Web应用
- 15.6 Java与CGI的沟通
- 15.7 用JDBC连接数据库
- 15.8 远程方法
- 15.9 总结
- 15.10 练习
- 第16章 设计模式
- 16.1 模式的概念
- 16.2 观察器模式
- 16.3 模拟垃圾回收站
- 16.4 改进设计
- 16.5 抽象的应用
- 16.6 多重分发
- 16.7 访问器模式
- 16.8 RTTI真的有害吗
- 16.9 总结
- 16.10 练习
- 第17章 项目
- 17.1 文字处理
- 17.2 方法查找工具
- 17.3 复杂性理论
- 17.4 总结
- 17.5 练习
- 附录A 使用非JAVA代码
- 附录B 对比C++和Java
- 附录C Java编程规则
- 附录D 性能
- 附录E 关于垃圾收集的一些话
- 附录F 推荐读物