友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第140部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
杂,就制作更多的对象”。尽管听起来有些暧昧,且简单得可笑,但这确实是我知道的最有用一条准则(大
家以后会注意到“制作更多的对象”经常等同于“添加另一个层次的迂回”)。一般情况下,如果发现一个
地方充斥着大量繁复的代码,就需要考虑什么类能使它显得清爽一些。用这种方式整理系统,往往会得到一
个更好的结构,也使程序更加灵活。
首先考虑Trash 对象首次创建的地方,这是main()里的一个switch语句:
for(int i = 0; i 《 30; i++)
switch((int)(Math。random() * 3)) {
case 0 :
bin。addElement(new
Aluminum(Math。random() * 100));
break;
case 1 :
bin。addElement (new
Paper(Math。random() * 100));
break;
case 2 :
bin。addElement(new
Glass(Math。random() * 100));
}
这些代码显然“过于复杂”,也是新类型加入时必须改动代码的场所之一。如果经常都要加入新类型,那么
更好的方案就是建立一个独立的方法,用它获取所有必需的信息,并创建一个句柄,指向正确类型的一个对
象——已经上溯造型到一个Trash 对象。在《Design Patterns》中,它被粗略地称呼为“创建范式”。要在
这里应用的特殊范式是 Factory 方法的一种变体。在这里,Factory 方法属于 Trash 的一名static (静态)
成员。但更常见的一种情况是:它属于衍生类中一个被过载的方法。
595
…………………………………………………………Page 597……………………………………………………………
Factory 方法的基本原理是我们将创建对象所需的基本信息传递给它,然后返回并等候句柄(已经上溯造型
至基础类型)作为返回值出现。从这时开始,就可以按多形性的方式对待对象了。因此,我们根本没必要知
道所创建对象的准确类型是什么。事实上,Factory 方法会把自己隐藏起来,我们是看不见它的。这样做可
防止不慎的误用。如果想在没有多形性的前提下使用对象,必须明确地使用RTTI 和指定造型。
但仍然存在一个小问题,特别是在基础类中使用更复杂的方法(不是在这里展示的那种),且在衍生类里过
载(覆盖)了它的前提下。如果在衍生类里请求的信息要求更多或者不同的参数,那么该怎么办呢?“创建
更多的对象”解决了这个问题。为实现Factory 方法,Trash 类使用了一个新的方法,名为 factory。为了将
创建数据隐藏起来,我们用一个名为 Info 的新类包含 factory 方法创建适当的 Trash 对象时需要的全部信
息。下面是 Info 一种简单的实现方式:
class Info {
int type;
// Must change this to add another type:
static final int MAX_NUM = 4;
double data;
Info(int typeNum; double dat) {
type = typeNum % MAX_NUM;
data = dat;
}
}
Info 对象唯一的任务就是容纳用于factory()方法的信息。现在,假如出现了一种特殊情况,factory()需要
更多或者不同的信息来新建一种类型的Trash 对象,那么再也不需要改动factory()了。通过添加新的数据
和构建器,我们可以修改 Info 类,或者采用子类处理更典型的面向对象形式。
用于这个简单示例的factory()方法如下:
static Trash factory(Info i) {
switch(i。type) {
default: // To quiet the piler
case 0:
return new Aluminum(i。data);
case 1:
return new Paper(i。data);
case 2:
return new Glass(i。data);
// Two lines here:
case 3:
return new Cardboard(i。data);
}
}
在这里,对象的准确类型很容易即可判断出来。但我们可以设想一些更复杂的情况,factory()将采用一种复
杂的算法。无论如何,现在的关键是它已隐藏到某个地方,而且我们在添加新类型时知道去那个地方。
新对象在main()中的创建现在变得非常简单和清爽:
for(int i = 0; i 《 30; i++)
bin。addElement(
Trash。factory(
new Info(
(int)(Math。random() * Info。MAX_NUM);
Math。random() * 100)));
596
…………………………………………………………Page 598……………………………………………………………
我们在这里创建了一个 Info 对象,用于将数据传入 factory() ;后者在内存堆中创建某种Trash 对象,并返
回添加到Vector bin 内的句柄。当然,如果改变了参数的数量及类型,仍然需要修改这个语句。但假如
Info 对象的创建是自动进行的,也可以避免那个麻烦。例如,可将参数的一个 Vector 传递到 Info 对象的构
建器中(或直接传入一个factory()调用)。这要求在运行期间对参数(自变量)进行分析与检查,但确实
提供了非常高的灵活程度。
大家从这个代码可看出 Factory 要负责解决的“领头变化”问题:如果向系统添加了新类型(发生了变
化),唯一需要修改的代码在 Factory 内部,所以Factory 将那种变化的影响隔离出来了。
16。4。2 用于原型创建的一个范式
上述设计方案的一个问题是仍然需要一个中心场所,必须在那里知道所有类型的对象:在factory()方法内
部。如果经常都要向系统添加新类型,factory()方法为每种新类型都要修改一遍。若确实对这个问题感到苦
恼,可试试再深入一步,将与类型有关的所有信息——包括它的创建过程——都移入代表那种类型的类内
部。这样一来,每次新添一种类型的时候,需要做的唯一事情就是从一个类继承。
为将涉及类型创建的信息移入特定类型的 Trash 里,必须使用“原型”(prototype)范式(来自《Design
Patterns》那本书)。这里最基本的想法是我们有一个主控对象序列,为自己感兴趣的每种类型都制作一
个。这个序列中的对象只能用于新对象的创建,采用的操作类似内建到Java 根类Object 内部的clone()机
制。在这种情况下,我们将克隆方法命名为tClone()。准备创建一个新对象时,要事先收集好某种形式的信
息,用它建立我们希望的对象类型。然后在主控序列中遍历,将手上的信息与主控序列中原型对象内任何适
当的信息作对比。若找到一个符合自己需要的,就克隆它。
采用这种方案,我们不必用硬编码的方式植入任何创建信息。每个对象都知道如何揭示出适当的信息,以及
如何对自身进行克隆。所以一种新类型加入系统的时候,factory()方法不需要任何改变。
为解决原型的创建问题,一个方法是添加大量方法,用它们支持新对象的创建。但在 Java 1。1 中,如果拥有
指向Class 对象的一个句柄,那么它已经提供了对创建新对象的支持。利用Java 1。1 的“反射”(已在第
11章介绍)技术,即便我们只有指向 Class 对象的一个句柄,亦可正常地调用一个构建器。这对原型问题的
解决无疑是个完美的方案。
原型列表将由指向所有想创建的Class 对象的一个句柄列表间接地表示。除此之外,假如原型处理失败,则
factory()方法会认为由于一个特定的Class 对象不在列表中,所以会尝试装载它。通过以这种方式动态装载
原型,Trash 类根本不需要知道自己要操纵的是什么类型。因此,在我们添加新类型时不需要作出任何形式
的修改。于是,我们可在本章剩余的部分方便地重复利用它。
//: Trash。java
// Base class for Trash recycling examples
package c16。trash;
import java。util。*;
import java。lang。reflect。*;
public abstract class Trash {
private double weight;
Trash(double wt) { weight = wt; }
Trash() {}
public abstract double value();
public double weight() { return weight; }
// Sums the value of Trash in a bin:
public static void sumValue(Vector bin) {
Enumeration e = bin。elements();
double val = 0。0f;
while(e。hasMoreElements()) {
// One kind of RTTI:
// A dynamically…checked cast
Trash t = (Trash)e。nextElement();
val += t。weight() * t。value();
System。out。println(
597
…………………………………………………………Page 599……………………………………………………………
〃weight of 〃 +
// Using RTTI to get type
// information about the class:
t。getClass()。getName() +
〃 = 〃 + t。weight());
}
System。out。println(〃Total value = 〃 + val);
}
// Remainder of class provides support for
// prototyping:
public static class PrototypeNotFoundException
extends Exception {}
public static class CannotCreateTrashException
extends Exception {}
private static Vector trashTypes =
new Vector();
public static Trash factory(Info info)
throws PrototypeNotFoundException;
CannotCreateTrashException {
for(int i = 0; i 《 trashTypes。size(); i++) {
// Somehow determine the new type
// to create; and create one:
Class tc =
(Class)trashTypes。elementAt(i);
if (tc。getName()。indexOf(info。id) != …1) {
try {
// Get the dynamic constructor method
// that takes a double argument:
Constructor ctor =
tc。getConstructor(
new Class'' {double。class});
// Call the constructor to create a
// new object:
return (Trash)ctor。newInstance(
new Object''{new Double(info。data)});
} catch(Exception ex) {
ex。printStackTrace();
throw new CannotCreateTrashException();
}
}
}
// Class was not in the list。 Try to load it;
// but it must be in your class path!
try {
System。out。println(〃Loading 〃 + info。id);
trashTypes。addElement(
Class。forName(info。id));
} catch(Exception e) {
e。printStackTrace();
throw new PrototypeNotFoundException();
}
// Loaded successfully。 Recursive call
598
…………………………………………………………Page 600……………………………………………………………
// should work this time:
return factory(info);
}
public static class Info {
public String id;
public double data;
public Info(String name; double data) {
id = name;
this。data = data;
}
}
} ///:~
基本Trash 类和 sumValue()还是象往常一样。这个类剩下的部分支持原型范式。大家首先会看到两个内部类
(被设为static 属性,使其成为只为代码组织目的而存在的内部类),它们描述了可能出现的违例。在它后
面跟随的是一个Vector trashTypes,用于容纳 Class 句柄。
在Trash。factory()中,Info 对象 id (Info 类的另一个版本,与前面讨论的不同)内部的 String 包含了要
创建的那种 Trash 的类型名称。这个 String 会与列表中的Class 名比较。若存在相符的,那便是要创建的对
象。当然,还有很多方法可以决定我们想创建的对象。之所以要采用这种方法,是因为从一个文件读入的信
息可以转换成对象。
发现自己要创建的Trash (垃圾)种类后,接下来就轮到“反射”方法大显身手了。getConstructor()方法
需要取得自己的参数——由Class 句柄构成的一个数组。这个数组代表着不同的参数,并按它们正确的顺序
排列,以便我们查找的构建器使用。在这儿,该数组是用 Java 1。1 的数组创建语法动态创建的:
new Class'' {double。class}
这个代码假定所有 Trash 类型都有一个需要 double 数值的构建器(注意 double。class 与 Double。class 是不
同的)。若考虑一种更灵活的方案,亦可调用 getConstructors(),令其返回可用构建器的一个数组。
从getConstructors()返回的是指向一个 Constructor 对象的句柄(该对象是 java。lang。reflect 的一部
分)。我们用方法 newInstance() 动态地调用构建器。该方法需要获取包含了实际参数的一个Object 数组。
这个数组同样是按 Java 1。1 的语法创建的:
new Object'' {new Double(info。data)}
在这种情况下,double 必须置入一个封装(容器)类的内部,使其真正成为这个对象数组的一部分。通过调
用newInstance() ,会提取出 double,但大家可能会觉得稍微有些迷惑——参数既可能是 double,也可能是
Double,但在调用的时候必须用Double 传
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!