友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第104部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
double weight = Double。valueOf(
buf。substring(buf。indexOf(':') + 1)
。trim())。doubleValue();
bin。addTrash(
Trash。factory(
new Trash。Info(type; weight)));
}
data。close();
} catch(IOException e) {
e。printStackTrace();
} catch(Exception e) {
e。printStackTrace();
}
}
// Special case to handle Vector:
public static void
fillBin(String filename; Vector bin) {
fillBin(filename; new FillableVector(bin));
}
} ///:~
在RecycleA。java 中,我们用一个Vector 容纳Trash 对象。然而,亦可考虑采用其他集合类型。为做到这一
点,fillBin()的第一个版本将获取指向一个 Fillable 的句柄。后者是一个接口,用于支持一个名为
addTrash()的方法:
//: Fillable。java
// Any object that can be filled with Trash
package c16。trash;
public interface Fillable {
void addTrash(Trash t);
} ///:~
支持该接口的所有东西都能伴随fillBin 使用。当然,Vector 并未实现Fillable ,所以它不能工作。由于
Vector 将在大多数例子中应用,所以最好的做法是添加另一个过载的 fillBin()方法,令其以一个 Vector 作
为参数。利用一个适配器(Adapter)类,这个Vector 可作为一个 Fillable 对象使用:
//: FillableVector。java
// Adapter that makes a Vector Fillable
package c16。trash;
import java。util。*;
public class FillableVector implements Fillable {
private Vector v;
public FillableVector(Vector vv) { v = vv; }
public void addTrash(Trash t) {
v。addElement(t);
}
602
…………………………………………………………Page 604……………………………………………………………
} ///:~
可以看到,这个类唯一的任务就是负责将 Fillable 的addTrash()同Vector 的addElement()方法连接起来。
利用这个类,已过载的 fillBin()方法可在ParseTrash。java 中伴随一个Vector 使用:
public static void
fillBin(String filename; Vector bin) {
fillBin(filename; new FillableVector(bin));
}
这种方案适用于任何频繁用到的集合类。除此以外,集合类还可提供它自己的适配器类,并实现 Fillable
(稍后即可看到,在DynaTrash。java 中)。
3。 原型机制的重复应用
现在,大家可以看到采用原型技术的、修订过的RecycleA。java 版本了:
//: RecycleAP。java
// Recycling with RTTI and Prototypes
package c16。recycleap;
import c16。trash。*;
import java。util。*;
public class RecycleAP {
public static void main(String'' args) {
Vector bin = new Vector();
// Fill up the Trash bin:
ParseTrash。fillBin(〃Trash。dat〃; bin);
Vector
glassBin = new Vector();
paperBin = new Vector();
alBin = new Vector();
Enumeration sorter = bin。elements();
// Sort the Trash:
while(sorter。hasMoreElements()) {
Object t = sorter。nextElement();
// RTTI to show class membership:
if(t instanceof Aluminum)
alBin。addElement(t);
if(t instanceof Paper)
paperBin。addElement(t);
if(t instanceof Glass)
glassBin。addElement(t);
}
Trash。sumValue(alBin);
Trash。sumValue(paperBin);
Trash。sumValue(glassBin);
Trash。sumValue(bin);
}
} ///:~
所有Trash 对象——以及ParseTrash 及支撑类——现在都成为名为c16。trash 的一个包的一部分,所以它们
可以简单地导入。
无论打开包含了 Trash 描述信息的数据文件,还是对那个文件进行解析,所有涉及到的操作均已封装到
603
…………………………………………………………Page 605……………………………………………………………
static (静态)方法ParseTrash。fillBin()里。所以它现在已经不是我们设计过程中要注意的一个重点。在
本章剩余的部分,大家经常都会看到无论添加的是什么类型的新类,ParseTrash。fillBin()都会持续工作,
不会发生改变,这无疑是一种优良的设计方案。
提到对象的创建,这一方案确实已将新类型加入系统所需的变动严格地“本地化”了。但在使用 RTTI 的过程
中,却存在着一个严重的问题,这里已明确地显露出来。程序表面上工作得很好,但却永远侦测到不能“硬
纸板”(Cardboard)这种新的废品类型——即使列表里确实有一个硬纸板类型!之所以会出现这种情况,完
全是由于使用了RTTI 的缘故。RTTI 只会查找那些我们告诉它查找的东西。RTTI 在这里错误的用法是“系统
中的每种类型”都进行了测试,而不是仅测试一种类型或者一个类型子集。正如大家以后会看到的那样,在
测试每一种类型时可换用其他方式来运用多形性特征。但假如以这种形式过多地使用 RTTI,而且又在自己的
系统里添加了一种新类型,很容易就会忘记在程序里作出适当的改动,从而埋下以后难以发现的 Bug。因
此,在这种情况下避免使用RTTI 是很有必要的,这并不仅仅是为了表面好看——也是为了产生更易维护的代
码。
16。5 抽象的应用
走到这一步,接下来该考虑一下设计方案剩下的部分了——在哪里使用类?既然归类到垃圾箱的办法非常不
雅且过于暴露,为什么不隔离那个过程,把它隐藏到一个类里呢?这就是著名的“如果必须做不雅的事情,
至少应将其本地化到一个类里”规则。看起来就象下面这样:
现在,只要一种新类型的Trash 加入方法,对TrashSorter 对象的初始化就必须变动。可以想象,
TrashSorter 类看起来应该象下面这个样子:
class TrashSorter extends Vector {
void sort(Trash t) { /* 。。。 */ }
}
也就是说,TrashSorter 是由一系列句柄构成的 Vector (系列),而那些句柄指向的又是由Trash 句柄构成
的Vector;利用 addElement(),可以安装新的TrashSorter,如下所示:
TrashSorter ts = new TrashSorter();
ts。addElement(new Vector());
但是现在,sort()却成为一个问题。用静态方式编码的方法如何应付一种新类型加入的事实呢?为解决这个
问题,必须从sort()里将类型信息删除,使其需要做的所有事情就是调用一个通用方法,用它照料涉及类型
处理的所有细节。这当然是对一个动态绑定方法进行描述的另一种方式。所以sort()会在序列中简单地遍
历,并为每个Vector 都调用一个动态绑定方法。由于这个方法的任务是收集它感兴趣的垃圾片,所以称之为
grab(Trash)。结构现在变成了下面这样:
604
…………………………………………………………Page 606……………………………………………………………
其中,TrashSorter 需要调用每个 grab()方法;然后根据当前Vector 容纳的是什么类型,会获得一个不同的
结果。也就是说,Vector 必须留意自己容纳的类型。解决这个问题的传统方法是创建一个基础“Trash
bin”(垃圾筒)类,并为希望容纳的每个不同的类型都继承一个新的衍生类。若Java 有一个参数化的类型
机制,那就也许是最直接的方法。但对于这种机制应该为我们构建的各个类,我们不应该进行麻烦的手工编
码,以后的“观察”方式提供了一种更好的编码方式。
OOP 设计一条基本的准则是“为状态的变化使用数据成员,为行为的变化使用多性形”。对于容纳Paper (纸
张)的Vector,以及容纳 Glass (玻璃)的Vector,大家最开始或许会认为分别用于它们的 grab()方法肯定
会产生不同的行为。但具体如何却完全取决于类型,而不是其他什么东西。可将其解释成一种不同的状态,
而且由于Java 有一个类可表示类型(Class),所以可用它判断特定的Tbin 要容纳什么类型的 Trash。
用于Tbin 的构建器要求我们为其传递自己选择的一个Class。这样做可告诉Vector 它希望容纳的是什么类
型。随后,grab()方法用 Class BinType 和 RTTI 来检查我们传递给它的 Trash 对象是否与它希望收集的类型
相符。
下面列出完整的解决方案。设定为注释的编号(如*1*)便于大家对照程序后面列出的说明。
//: RecycleB。java
// Adding more objects to the recycling problem
package c16。recycleb;
import c16。trash。*;
import java。util。*;
// A vector that admits only the right type:
class Tbin extends Vector {
Class binType;
Tbin(Class binType) {
this。binType = binType;
}
boolean grab(Trash t) {
// paring class types:
if(t。getClass()。equals(binType)) {
addElement(t);
return true; // Object grabbed
}
return false; // Object not grabbed
}
}
class TbinList extends Vector { //(*1*)
boolean sort(Trash t) {
Enumeration e = elements();
605
…………………………………………………………Page 607……………………………………………………………
while(e。hasMoreElements()) {
Tbin bin = (Tbin)e。nextElement();
if(bin。grab(t)) return true;
}
return false; // bin not found for t
}
void sortBin(Tbin bin) { // (*2*)
Enumeration e = bin。elements();
while(e。hasMoreElements())
if(!sort((Trash)e。nextElement()))
System。out。println(〃Bin not found〃);
}
}
public class RecycleB {
static Tbin bin = new Tbin (Trash。class);
public static void main(String'' args) {
// Fill up the Trash bin:
ParseTrash。fillBin(〃Trash。dat〃; bin);
TbinList trashBins = new TbinList();
trashBins。addElement(
new Tbin(Aluminum。class));
trashBins。addElement(
new Tbin(Paper。class));
trashBins。addElement(
new Tbin(Glass。class));
// add one line here: (*3*)
trashBins。addElement(
new Tbin(Cardboard。class));
trashBins。sortBin(bin); // (*4*)
Enumeration e = trashBins。elements();
while(e。hasMoreElements()) {
Tbin b = (Tbin)e。nextElement();
Trash。sumValue(b);
}
Trash。sumValue(bin);
}
} ///:~
(1) TbinList容纳一系列 Tbin 句柄,所以在查找与我们传递给它的Trash 对象相符的情况时,sort()能通
过Tbin 继承。
(2) sortBin()允许我们将一个完整的 Tbin 传递进去,而且它会在 Tbin 里遍历,挑选出每种Trash,并将其
归类到特定的Tbin 中。请注意这些代码的通用性:新类型加入时,它本身不需要任何改动。只要新类型加入
(或发生其他事件)时大量代码都不需要变化,就表明我们设计的是一个容易扩展的系统。
(3) 现在可以体会添加新类型有多么容易了。为支持添加,只需要改动几行代码。如确实有必要,甚至可以
进一步地改进设计,使更多的代码都保持“固定”。
(4) 一个方法调用使bin 的内容归类到对应的、特定类型的垃圾筒里。
606
…………………………………………………………Page 608……………………………………………………………
16。6 多重派遣
上述设计方案肯定是令人满意的。系统内新类型的加入涉及添加或修改不同的类,但没有必要在系统内对代
码作大范围的改动。除此以外,RTTI 并不象它在 RecycleA。java 里那样被不当地使用。然而,我们仍然有可
能更深入一步,以最“纯”的角度来看待RTTI,考虑如何在垃圾分类系统中将它完全消灭。
为达到这个目标,首先必须认识到:对所有与不同类型有特殊关联的活动来说——比如侦测一种垃圾的具体
类型,并把它置入适当的垃圾筒里——这些活动都应当通过多形性以及动态绑定加以控制。
以前的例子都是先按类型排序,再对属于某种特殊类型的一系列元素进行操作。现在一旦需要操作特定的类
型,就请先停下来想一想。事实上,多形性(动态绑定的方法调用)整个的宗旨就是帮我们管理与不同类型
有特殊关联的信息。既然如此,为什么还要自己去检查类型呢?
答案在于大家或许不以为然的一个道理:Java 只执行单一派遣。也就是说,假如对多个类型未知的对象执行
某项操作,Java 只会为那些类型中的一种调用动态绑定机制。这当然不能解决问题,所以最后不得不人工判
断某些类型,才能有效地产生自己的动态绑定行为。
为解决这个缺陷,我们需要用到“多重派遣”机制,这意味着需要建立一个配置,使单一方法调用能产生多
个动态方法调用,从而在一次处理过程中正确判断出多种类型。为达到这个要求,需要对多个类型结构进行
操作:每一次派遣都需要一个类型结构。下面的例子将对两个结构进行操作:现有的 Trash 系列以及由垃圾
筒(Trash Bin)的类型构成的一个系列——不同的垃圾或废品将置入这些筒内。第二个分级结构并非绝对显
然的。在这种情况下,我们需要人为地创建它,以执行多重派遣(由于本例只涉及两次派遣,所以称为“双
重派遣”)。
16。6。1 实现双重派遣
记住多形性只能通过方法调用才能表现出来,所以假如想使双重派遣正确进行,必须执行两个方法调用:在
每种结构中都用一个来判断其中的类型。在Trash 结构中,将使用一个新的方法调用 addToBin(),它采用的
参数是由TypeBin 构成的一个数组。那个方法将在数组中遍历,尝试将自己加入适当的垃圾筒,这里正是双
重派遣发生的地方。
新建立的分级结构是TypeBin,其中包含了它自己的一个方法,名为add(),而且也应用了多形性。但要注意
一个新特点:add()已进行了“过载”处理,可接受不同的垃圾类型作为参数。因此,双重满足机制的一个关
键点是它也要涉及到过载。
程序的重新设计也带来了一个问题:现在的基础类 Trash 必须包含一个addToBin()方法。为解决这个问题,
一个最直接的办法是复制所有代码,并修改基础类。然而,假如没有对源码的控制权,那么还有另一个办法
可以考虑:将addToBin()方法置入一个接口内部,保持 Trash 不变,并继承新的、特殊的类型Aluminum ,
Paper,Glass 以及Cardboard。我们在这里准备采取后一个办法。
607
…………………………………………………………Page 609……………………………………………………………
这个设计方案中用到的大多数类都必须设为public (公用)属性,所以它们放置于自己的类内。下面列出接
口代码:
//: TypedBinMember。java
// An interface for adding the double dispatching
// method to the trash hierarchy without
// modifying the original hierarchy。
package c16。doubledispatch;
interface TypedBinMember {
// The new method:
boolean addToBin(TypedBin'' tb);
} ///:~
在Aluminum ,Paper,Glass 以及Cardboard 每个特定的子类型内,都会实现接口 TypeBinMember 的
addToBin()方法,但每种情况下使用的代码“似乎”都是完全一样的:
//: DDAluminum。java
// Aluminum for double dispatching
package c16。doubledispatch;
import c16。trash。*;
public class DDAluminum extends Aluminum
implements TypedBinMember {
public DDAluminum(double wt) { super(wt); }
public boolean addToBin(TypedBin'' tb) {
for(int i = 0; i 《 tb。length; i++)
if(tb'i'。add(this))
return true;
return false;
}
} ///:~
//: DDPaper。java
// Paper for double dispatching
package c16。doubledispatch;
import c16。trash。*;
public class DDPaper extends Paper
implements TypedBinMember {
public DDPaper(double wt) { super(wt); }
public boolean addToBin(TypedBin'' tb) {
for(int i = 0; i 《 tb。length; i++)
if(tb'i'。add(this))
return true;
return false;
}
} ///:~
//: DDGlass。java
// Glass for double dispatching
package c16。doubledispatch;
import c16。trash。*;
608
…………………………………………………………Page 610……………………………………………………………
public class DDGlass extends Glass
implements TypedBinMember {
public DDGlass(double wt) { super(wt); }
public boolean addToBin(TypedBin'' tb) {
for(int i = 0; i 《 tb。length; i++)
if(tb'i'。add(this))
return true;
return false;
}
} ///:~
//: DDCardboard。java
// Cardboard for double dispatching
package c16。doubledispatch;
import c16。trash。*;
public class DDCardboard extends Cardboard
implements Ty
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!