⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter16.htm

📁 一本Java由初级到高级的编程书籍
💻 HTM
📖 第 1 页 / 共 4 页
字号:
16.7 访问器范式<br>
接下来,让我们思考如何将具有完全不同目标的一个设计范式应用到垃圾归类系统。<br>
对这个范式,我们不再关心在系统中加入新型Trash时的优化。事实上,这个范式使新型Trash的添加显得更加复杂。假定我们有一个基本类结构,它是固定不变的;它或许来自另一个开发者或公司,我们无权对那个结构进行任何修改。然而,我们又希望在那个结构里加入新的多形性方法。这意味着我们一般必须在基础类的接口里添加某些东西。因此,我们目前面临的困境是一方面需要向基础类添加方法,另一方面又不能改动基础类。怎样解决这个问题呢?<br>
“访问器”(Visitor)范式使我们能扩展基本类型的接口,方法是创建类型为Visitor的一个独立的类结构,对以后需对基本类型采取的操作进行虚拟。基本类型的任务就是简单地“接收”访问器,然后调用访问器的动态绑定方法。看起来就象下面这样:<br>
<br>
945页图<br>
<br>
现在,假如v是一个指向Aluminum(铝制品)的Visitable句柄,那么下述代码:<br>
PriceVisitor pv = new PriceVisitor();<br>
v.accept(pv);<br>
会造成两个多形性方法调用:第一个会选择accept()的Aluminum版本;第二个则在accept()里——用基础类Visitor句柄v动态调用visit()的特定版本时。<br>
这种配置意味着可采取Visitor的新子类的形式将新的功能添加到系统里,没必要接触Trash结构。这就是“访问器”范式最主要的优点:可为一个类结构添加新的多形性功能,同时不必改动结构——只要安装好了accept()方法。注意这个优点在这儿是有用的,但并不一定是我们在任何情况下的首选方案。所以在最开始的时候,就要判断这到底是不是自己需要的方案。<br>
现在注意一件没有做成的事情:访问器方案防止了从主控Trash序列向单独类型序列的归类。所以我们可将所有东西都留在单主控序列中,只需用适当的访问器通过那个序列传递,即可达到希望的目标。尽管这似乎并非访问器范式的本意,但确实让我们达到了很希望达到的一个目标(避免使用RTTI)。<br>
访问器范式中的双生派遣负责同时判断Trash以及Visitor的类型。在下面的例子中,大家可看到Visitor的两种实现方式:PriceVisitor用于判断总计及价格,而WeightVisitor用于跟踪重量。<br>
可以看到,所有这些都是用回收程序一个新的、改进过的版本实现的。而且和DoubleDispatch.java一样,Trash类被保持孤立,并创建一个新接口来添加accept()方法:<br>
<br>
946页上程序<br>
<br>
Aluminum,Paper,Glass以及Cardboard的子类型实现了accept()方法:<br>
<br>
946-947页程序<br>
<br>
由于Visitor基础类没有什么需要实在的东西,可将其创建成一个接口:<br>
<br>
947-948页程序<br>
<br>
程序剩余的部分将创建特定的Visitor类型,并通过一个Trash对象列表发送它们:<br>
<br>
948-951页程序<br>
<br>
注意main()的形状已再次发生了变化。现在只有一个垃圾(Trash)筒。两个Visitor对象被接收到序列中的每个元素内,它们会完成自己份内的工作。Visitor跟踪它们自己的内部数据,计算出总重和价格。<br>
最好,将东西从序列中取出的时候,除了不可避免地向Trash造型以外,再没有运行期的类型验证。若在Java里实现了参数化类型,甚至那个造型操作也可以避免。<br>
对比之前介绍过的双重派遣方案,区分这两种方案的一个办法是:在双重派遣方案中,每个子类创建时只会过载其中的一个过载方法,即add()。而在这里,每个过载的visit()方法都必须在Visitor的每个子类中进行过载。<br>
<br>
1. 更多的结合?<br>
这里还有其他许多代码,Trash结构和Visitor结构之间存在着明显的“结合”(Coupling)关系。然而,在它们所代表的类集内部,也存在着高度的凝聚力:都只做一件事情(Trash描述垃圾或废品,而Visitor描述对垃圾采取什么行动)。作为一套优秀的设计方案,这无疑是个良好的开端。当然就目前的情况来说,只有在我们添加新的Visitor类型时才能体会到它的好处。但在添加新类型的Trash时,它却显得有些碍手碍脚。<br>
类与类之间低度的结合与类内高度的凝聚无疑是一个重要的设计目标。但只要稍不留神,就可能妨碍我们得到一个本该更出色的设计。从表面看,有些类不可避免地相互间存在着一些“亲密”关系。这种关系通常是成对发生的,可以叫作“对联”(Couplet)——比如集合和继承器(Enumeration)。前面的Trash-Visitor对似乎也是这样的一种“对联”。<br>
<br>
16.8 RTTI真的有害吗<br>
本章的各种设计方案都在努力避免使用RTTI,这或许会给大家留下“RTTI有害”的印象(还记得可怜的goto吗,由于给人印象不佳,根本就没有放到Java里来)。但实际情况并非绝对如此。正确地说,应该是RTTI使用不当才“有害”。我们之所以想避免RTTI的使用,是由于它的错误运用会造成扩展性受到损害。而我们事前提出的目标就是能向系统自由加入新类型,同时保证对周围的代码造成尽可能小的影响。由于RTTI常被滥用(让它查找系统中的每一种类型),会造成代码的扩展能力大打折扣——添加一种新类型时,必须找出使用了RTTI的所有代码。即使仅遗漏了其中的一个,也不能从编译器那里得到任何帮助。<br>
然而,RTTI本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备引入一种新工具,我把它叫作TypeMap。其中包含了一个Hashtable(散列表),其中容纳了多个Vector,但接口非常简单:可以添加(add())一个新对象,可以获得(get())一个Vector,其中包含了属于某种特定类型的所有对象。对于这个包含的散列表,它的关键在于对应的Vector里的类型。这种设计方案的优点(根据Larry 
O'Brien的建议)是在遇到一种新类型的时候,TypeMap会动态加入一种新类型。所以不管什么时候,只要将一种新类型加入系统(即使在运行期间添加),它也会正确无误地得以接受。<br>
我们的例子同样建立在c16.Trash这个“包”(Package)内的Trash类型结构的基础上(而且那儿使用的Trash.dat文件可以照搬到这里来)。<br>
<br>
952-953页程序<br>
<br>
尽管功能很强,但对TypeMap的定义是非常简单的。它只是包含了一个散列表,同时add()负担了大部分的工作。添加一个新类型时,那种类型的Class对象的句柄会被提取出来。随后,利用这个句柄判断容纳了那类对象的一个Vector是否已存在于散列表中。如答案是肯定的,就提取出那个Vector,并将对象加入其中;反之,就将Class对象及新Vector作为一个“键-值”对加入。<br>
利用keys(),可以得到对所有Class对象的一个“枚举”(Enumeration),而且可用get(),可通过Class对象获取对应的Vector。<br>
filler()方法非常有趣,因为它利用了ParseTrash.fillBin()的设计——不仅能尝试填充一个Vector,也能用它的addTrash()方法试着填充实现了Fillable(可填充)接口的任何东西。filter()需要做的全部事情就是将一个句柄返回给实现了Fillable的一个接口,然后将这个句柄作为参数传递给fillBin(),就象下面这样:<br>
ParseTrash.fillBin(&quot;Trash.dat&quot;, bin.filler());<br>
为产生这个句柄,我们采用了一个“匿名内部类”(已在第7章讲述)。由于根本不需要用一个已命名的类来实现Fillable,只需要属于那个类的一个对象的句柄即可,所以这里使用匿名内部类是非常恰当的。<br>
对这个设计,要注意的一个地方是尽管没有设计成对归类加以控制,但在fillBin()每次进行归类的时候,都会将一个Trash对象插入bin。<br>
通过前面那些例子的学习,DynaTrash类的大多数部分都应当非常熟悉了。这一次,我们不再将新的Trash对象置入类型Vector的一个bin内。由于bin的类型为TypeMap,所以将垃圾(Trash)丢进垃圾筒(Bin)的时候,TypeMap的内部归类机制会立即进行适当的分类。在TypeMap里遍历并对每个独立的Vector进行操作,这是一件相当简单的事情:<br>
<br>
954页程序<br>
<br>
就象大家看到的那样,新类型向系统的加入根本不会影响到这些代码,亦不会影响TypeMap中的代码。这显然是解决问题最圆满的方案。尽管它确实严重依赖RTTI,但请注意散列表中的每个键-值对都只查找一种类型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根本就没有什么代码需要添加。<br>
<br>
16.9 总结<br>
从表面看,由于象TrashVisitor.java这样的设计包含了比早期设计数量更多的代码,所以会留下效率不高的印象。试图用各种设计方案达到什么目的应该是我们考虑的重点。设计范式特别适合“将发生变化的东西与保持不变的东西隔离开”。而“发生变化的东西”可以代表许多种变化。之所以发生变化,可能是由于程序进入一个新环境,或者由于当前环境的一些东西发生了变化(例如“用户希望在屏幕上当前显示的图示中添加一种新的几何形状”)。或者就象本章描述的那样,变化可能是对代码主体的不断改进。尽管废品分类以前的例子强调了新型Trash向系统的加入,但TrashVisitor.java允许我们方便地添加新功能,同时不会对Trash结构造成干扰。TrashVisitor.java里确实多出了许多代码,但在Visitor里添加新功能只需要极小的代价。如果经常都要进行此类活动,那么多一些代码也是值得的。<br>
变化序列的发现并非一件平常事;在程序的初始设计出台以前,那些分析家一般不可能预测到这种变化。除非进入项目设计的后期,否则一些必要的信息是不会显露出来的:有时只有进入设计或最终实现阶段,才能体会到对自己系统一个更深入或更不易察觉需要。添加新类型时(这是“回收”例子最主要的一个重点),可能会意识到只有自己进入维护阶段,而且开始扩充系统时,才需要一个特定的继承结构。<br>
通过设计范式的学习,大家可体会到最重要的一件事情就是本书一直宣扬的一个观点——多形性是OOP(面向对象程序设计)的全部——已发生了彻底的改变。换句话说,很难“获得”多形性;而一旦获得,就需要尝试将自己的所有设计都造型到一个特定的模子里去。<br>
设计范式要表明的观点是“OOP并不仅仅同多形性有关”。应当与OOP有关的是“将发生变化的东西同保持不变的东西分隔开来”。多形性是达到这一目的的特别重要的手段。而且假如编程语言直接支持多形性,那么它就显得尤其有用(由于直接支持,所以不必自己动手编写,从而节省大量的精力和时间)。但设计范式向我们揭示的却是达到基本目标的另一些常规途径。而且一旦熟悉并掌握了它的用法,就会发现自己可以做出更有创新性的设计。<br>
由于《Design Patterns》这本书对程序员造成了如此重要的影响,所以他们纷纷开始寻找其他范式。随着的时间的推移,这类范式必然会越来越多。JimCoplien(http://www.bell-labs.com/~cope主页作者)向我们推荐了这样的一些站点,上面有许多很有价值的范式说明:<br>
http://st-www.cs.uiuc.edu/users/patterns<br>
http://c2.com/cgi/wiki<br>
http://c2.com/ppr<br>
http://www.bell-labs.com/people/cope/Patterns/Process/index.html<br>
http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns<br>
http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic<br>
http://www.cs.wustl.edu/~schmidt/patterns.html<br>
http://www.espinc.com/patterns/overview.html<br>
同时请留意每年都要召开一届权威性的设计范式会议,名为PLOP。会议会出版许多学术论文,第三届已在1997年底召开过了,会议所有资料均由Addison-Wesley出版。<br>
<br>
16.10 练习<br>
(1) 将SingletonPattern.java作为起点,创建一个类,用它管理自己固定数量的对象。<br>
(2) 为TrashVisitor.java添加一个名为Plastic(塑料)的类。<br>
(3) 为DynaTrash.java同样添加一个Plastic(塑料)类。</p>

<!--msthemeseparator--><p align="center"><img src="../_themes/inmotion/inmhorsa.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmhorsa.gif" width="300" height="10"></p>

<p align="center"><a href="../../../../tppmsgs/msgs0.htm#1" tppabs="http://www.bruceeckel.com/">英文版主页</a> | <a href="../index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/index.htm">中文版主页</a> | <a href="../contents/index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/contents/index.htm">详细目录</a> 
| <a href="../about/index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/about/index.htm">关于译者</a></p>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -