《Java编程思想第4版[中文版](PDF格式)》第264章


RTTI 常被滥用(让它查找系统中的每一种类型),会造成代码的扩展能力大打折扣——添加一种新类型时, 
必须找出使用了RTTI 的所有代码。即使仅遗漏了其中的一个,也不能从编译器那里得到任何帮助。
然而,RTTI 本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备 
引入一种新工具,我把它叫作TypeMap。其中包含了一个Hashtable (散列表),其中容纳了多个Vector, 
但接口非常简单:可以添加(add())一个新对象,可以获得(get())一个Vector,其中包含了属于某种特 
定类型的所有对象。对于这个包含的散列表,它的关键在于对应的Vector 里的类型。这种设计方案的优点 
(根据Larry O"Brien 的建议)是在遇到一种新类型的时候,TypeMap 会动态加入一种新类型。所以不管什 
么时候,只要将一种新类型加入系统(即使在运行期间添加),它也会正确无误地得以接受。
我们的例子同样建立在 c16。Trash 这个“包”(Package)内的Trash 类型结构的基础上(而且那儿使用的 
Trash。dat 文件可以照搬到这里来)。
//: DynaTrash。java
// Using a Hashtable of Vectors and RTTI
// to automatically sort trash into
// vectors。 This solution; desp ite the
618 
…………………………………………………………Page 620……………………………………………………………
// use of RTTI; is extensible。
package c16。dynatrash;
import c16。trash。*;
import java。util。*;
// Generic TypeMap works in any situation:
class TypeMap {
private Hashtable t = new Hashtable();
public void add(Object o) {
Class type = o。getClass();
if(t。containsKey(type))
((Vector)t。get(type))。addElement(o);
else {
Vector v = new Vector();
v。addElement(o);
t。put(type;v);


public Vector get(Class type) {
return (Vector)t。get(type);

public Enumeration keys() { return t。keys(); }
// Returns handle to adapter class to allow
// callbacks from ParseTrash。fillBin():
public Fillable filler() {
// Anonymous inner class:
return new Fillable() {
public void addTrash(Trash t) { add(t); }
};


public class DynaTrash {
public static void main(String'' args) {
TypeMap bin = new TypeMap();
ParseTrash。fillBin(〃Trash。dat〃;bin。filler());
Enumeration keys = bin。keys();
while(keys。hasMoreElements())
Trash。sumValue(
bin。get((Class)keys。nextElement()));

} ///:~
尽管功能很强,但对TypeMap 的定义是非常简单的。它只是包含了一个散列表,同时add()负担了大部分的 
工作。添加一个新类型时,那种类型的Class 对象的句柄会被提取出来。随后,利用这个句柄判断容纳了那 
类对象的一个Vector 是否已存在于散列表中。如答案是肯定的,就提取出那个 Vector,并将对象加入其 
中;反之,就将Class 对象及新Vector 作为一个“键-值”对加入。
利用keys() ,可以得到对所有Class 对象的一个“枚举”(Enumeration),而且可用get(),可通过Class 
对象获取对应的Vector。
filler()方法非常有趣,因为它利用了 ParseTrash。fillBin()的设计——不仅能尝试填充一个Vector,也能 
用它的 addTrash()方法试着填充实现了 Fillable (可填充)接口的任何东西。filter() 需要做的全部事情就 
是将一个句柄返回给实现了Fillable 的一个接口,然后将这个句柄作为参数传递给 fillBin(),就象下面这 
619 
…………………………………………………………Page 621……………………………………………………………
样:
ParseTrash。fillBin(〃Trash。dat〃; bin。filler());
为产生这个句柄,我们采用了一个“匿名内部类”(已在第7 章讲述)。由于根本不需要用一个已命名的类 
来实现Fillable ,只需要属于那个类的一个对象的句柄即可,所以这里使用匿名内部类是非常恰当的。
对这个设计,要注意的一个地方是尽管没有设计成对归类加以控制,但在 fillBin()每次进行归类的时候, 
都会将一个 Trash 对象插入 bin。
通过前面那些例子的学习,DynaTrash 类的大多数部分都应当非常熟悉了。这一次,我们不再将新的 Trash 
对象置入类型Vector 的一个bin 内。由于bin 的类型为TypeMap,所以将垃圾(Trash)丢进垃圾筒(Bin) 
的时候,TypeMap 的内部归类机制会立即进行适当的分类。在TypeMap 里遍历并对每个独立的 Vector 进行操 
作,这是一件相当简单的事情:
Enumeration keys = bin。keys();
while(keys。hasMoreElements())
Trash。sumValue(
bin。get((Class)keys。nextElement()));
就象大家看到的那样,新类型向系统的加入根本不会影响到这些代码,亦不会影响TypeMap 中的代码。这显 
然是解决问题最圆满的方案。尽管它确实严重依赖 RTTI,但请注意散列表中的每个键-值对都只查找一种类 
型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根 
本就没有什么代码需要添加。
16。9 总结
从表面看,由于象 TrashVisitor。java 这样的设计包含了比早期设计数量更多的代码,所以会留下效率不高 
的印象。试图用各种设计方案达到什么目的应该是我们考虑的重点。设计范式特别适合“将发生变化的东西 
与保持不变的东西隔离开”。而“发生变化的东西”可以代表许多种变化。之所以发生变化,可能是由于程 
序进入一个新环境,或者由于当前环境的一些东西发生了变化(例如“用户希望在屏幕上当前显示的图示中 
添加一种新的几何形状”)。或者就象本章描述的那样,变化可能是对代码主体的不断改进。尽管废品分类 
以前的例子强调了新型Trash 向系统的加入,但TrashVisitor。java 允许我们方便地添加新功能,同时不会 
对Trash 结构造成干扰。TrashVisitor。java 里确实多出了许多代码,但在 Visitor 里添加新功能只需要极 
小的代价。如果经常都要进行此类活动,那么多一些代码也是值得的。
变化序列的发现并非一件平常事;在程序的初始设计出台以前,那些分析家一般不可能预测到这种变化。除 
非进入项目设计的后期,否则一些必要的信息是不会显露出来的:有时只有进入设计或最终实现阶段,才能 
体会到对自己系统一个更深入或更不易察觉需要。添加新类型时(这是“回收”例子最主要的一个重点), 
可能会意识到只有自己进入维护阶段,而且开始扩充系统时,才需要一个特定的继承结构。
通过设计范式的学习,大家可体会到最重要的一件事情就是本书一直宣扬的一个观点——多形性是OOP (面 
向对象程序设计)的全部——已发生了彻底的改变。换句话说,很难“获得”多形性;而一旦获得,就需要 
尝试将自己的所有设计都造型到一个特定的模子里去。
设计范式要表明的观点是“OOP 并不仅仅同多形性有关”。应当与 OOP 有关的是“将发生变化的东西同保持 
不变的东西分隔开来?
小说推荐
返回首页返回目录