本章的各种设计方案都在努力避免使用RTTI,这或许会给大家留下“RTTI有害”的印象(还记得可怜的goto吗,由于给人印象不佳,根本就没有放到Java里来)。但实际情况并非绝对如此。正确地说,应该是RTTI使用不当才“有害”。我们之所以想避免RTTI的使用,是由于它的错误运用会造成扩展性受到损害。而我们事前提出的目标就是能向系统自由加入新类型,同时保证对周围的代码造成尽可能小的影响。由于RTTI常被滥用(让它查找系统中的每一种类型),会造成代码的扩展能力大打折扣——添加一种新类型时,必须找出使用了RTTI的所有代码。即使仅遗漏了其中的一个,也不能从编译器那里得到任何帮助。
然而,RTTI本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备引入一种新工具,我把它叫作TypeMap。其中包含了一个Hashtable(散列表),其中容纳了多个Vector,但接口非常简单:可以添加(add())一个新对象,可以获得(get())一个Vector,其中包含了属于某种特定类型的所有对象。对于这个包含的散列表,它的关键在于对应的Vector里的类型。这种设计方案的优点(根据LarryO'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, despite the // 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(),就象下面这样:
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,但请注意散列表中的每个键-值对都只查找一种类型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根本就没有什么代码需要添加。
从表面看,由于象TrashVisitor.java这样的设计包含了比早期设计数量更多的代码,所以会留下效率不高的印象。试图用各种设计方案达到什么目的应该是我们考虑的重点。设计范式特别适合“将发生变化的东西与保持不变的东西隔离开”。而“发生变化的东西”可以代表许多种变化。之所以发生变化,可能是由于程序进入一个新环境,或者由于当前环境的一些东西发生了变化(例如“用户希望在屏幕上当前显示的图示中添加一种新的几何形状”)。或者就象本章描述的那样,变化可能是对代码主体的不断改进。尽管废品分类以前的例子强调了新型Trash向系统的加入,但TrashVisitor.java允许我们方便地添加新功能,同时不会对Trash结构造成干扰。TrashVisitor.java里确实多出了许多代码,但在Visitor里添加新功能只需要极小的代价。如果经常都要进行此类活动,那么多一些代码也是值得的。
变化序列的发现并非一件平常事;在程序的初始设计出台以前,那些分析家一般不可能预测到这种变化。除非进入项目设计的后期,否则一些必要的信息是不会显露出来的:有时只有进入设计或最终实现阶段,才能体会到对自己系统一个更深入或更不易察觉需要。添加新类型时(这是“回收”例子最主要的一个重点),可能会意识到只有自己进入维护阶段,而且开始扩充系统时,才需要一个特定的继承结构。
通过设计范式的学习,大家可体会到最重要的一件事情就是本书一直宣扬的一个观点——多形性是OOP(面向对象程序设计)的全部——已发生了彻底的改变。换句话说,很难“获得”多形性;而一旦获得,就需要尝试将自己的所有设计都造型到一个特定的模子里去。
设计范式要表明的观点是“OOP并不仅仅同多形性有关”。应当与OOP有关的是“将发生变化的东西同保持不变的东西分隔开来”。多形性是达到这一目的的特别重要的手段。而且假如编程语言直接支持多形性,那么它就显得尤其有用(由于直接支持,所以不必自己动手编写,从而节省大量的精力和时间)。但设计范式向我们揭示的却是达到基本目标的另一些常规途径。而且一旦熟悉并掌握了它的用法,就会发现自己可以做出更有创新性的设计。
由于《Design Patterns》这本书对程序员造成了如此重要的影响,所以他们纷纷开始寻找其他范式。随着的时间的推移,这类范式必然会越来越多。JimCoplien(http://www.bell-labs.com/~cope主页作者)向我们推荐了这样的一些站点,上面有许多很有价值的范式说明:
同时请留意每年都要召开一届权威性的设计范式会议,名为PLOP。会议会出版许多学术论文,第三届已在1997年底召开过了,会议所有资料均由Addison-Wesley出版。
(1) 将SingletonPattern.java作为起点,创建一个类,用它管理自己固定数量的对象。
(2) 为TrashVisitor.java添加一个名为Plastic(塑料)的类。
(3) 为DynaTrash.java同样添加一个Plastic(塑料)类。