提交 25034053 编写于 作者: W wizardforcel

16.1~4

上级 f8ae2259
......@@ -10,11 +10,11 @@
所以设计模式的最终目标就是将代码中变化的内容隔离开。如果从这个角度观察,就会发现本书实际已采用了一些设计模式。举个例子来说,继承可以想象成一种设计模式(类似一个由编译器实现的)。在都拥有同样接口(即保持不变的东西)的对象内部,它允许我们表达行为上的差异(即发生变化的东西)。合成亦可想象成一种模式,因为它允许我们修改——动态或静态——用于实现类的对象,所以也能修改类的运作方式。
在《Design Patterns》一书中,大家还能看到另一种模式:“迭代器”(即Iterator,Java 1.0和1.1不负责任地把它叫作Enumeration,即“枚举”;Java1.2的集合则改回了“迭代器”的称呼)。当我们在集合里遍历,逐个选择不同的元素时,迭代器可将集合的实现细节有效地隐藏起来。利用迭代器,可以编写出通用的代码,以便对一个序列里的所有元素采取某种操作,同时不必关心这个序列是如何构建的。这样一来,我们的通用代码即可伴随任何能产生迭代器的集合使用。
在《设计模式》一书中,大家还能看到另一种模式:“迭代器”(即`Iterator`,Java 1.0和1.1不负责任地把它叫作`Enumeration`,即“枚举”;Java1.2的集合则改回了“迭代器”的称呼)。当我们在集合里遍历,逐个选择不同的元素时,迭代器可将集合的实现细节有效地隐藏起来。利用迭代器,可以编写出通用的代码,以便对一个序列里的所有元素采取某种操作,同时不必关心这个序列是如何构建的。这样一来,我们的通用代码即可伴随任何能产生迭代器的集合使用。
16.1.1 单例
或许最简单的设计模式就是“单例”(Singleton),它能提供对象的一个(而且只有一个)实例。单例在Java库中得到了应用,但下面这个例子显得更直接一些:
或许最简单的设计模式就是“单例”(`Singleton`),它能提供对象的一个(而且只有一个)实例。单例在Java库中得到了应用,但下面这个例子显得更直接一些:
```
//: SingletonPattern.java
......@@ -52,22 +52,22 @@ public class SingletonPattern {
} ///:~
```
创建单例的关键就是防止客户程序员采用除由我们提供的之外的任何一种方式来创建一个对象。必须将所有构造器都设为private(私有),而且至少要创建一个构造器,以防止编译器帮我们自动同步一个默认构造器(它会自做聪明地创建成为“友好的”——friendly,而非private)。
创建单例的关键就是防止客户程序员采用除由我们提供的之外的任何一种方式来创建一个对象。必须将所有构造器都设为`private`(私有),而且至少要创建一个构造器,以防止编译器帮我们自动同步一个默认构造器(它会自做聪明地创建成为“友好的”——`friendly`,而非`private`)。
此时应决定如何创建自己的对象。在这儿,我们选择了静态创建的方式。但亦可选择等候客户程序员发出一个创建请求,然后根据他们的要求动态创建。不管在哪种情况下,对象都应该保存为“私有”属性。我们通过公用方法提供访问途径。在这里,getHandle()会产生指向Singleton的一个引用。剩下的接口(getValue()和setValue())属于普通的类接口。
此时应决定如何创建自己的对象。在这儿,我们选择了静态创建的方式。但亦可选择等候客户程序员发出一个创建请求,然后根据他们的要求动态创建。不管在哪种情况下,对象都应该保存为“私有”属性。我们通过公用方法提供访问途径。在这里,`getHandle()`会产生指向`Singleton`的一个引用。剩下的接口(`getValue()``setValue()`)属于普通的类接口。
Java也允许通过克隆(Clone)方式来创建一个对象。在这个例子中,将类设为final可禁止克隆的发生。由于Singleton是从Object直接继承的,所以clone()方法会保持protected(受保护)属性,不能够使用它(强行使用会造成编译期错误)。然而,假如我们是从一个类结构中继承,那个结构已经重载了clone()方法,使其具有public属性,并实现了Cloneable,那么为了禁止克隆,需要重载clone(),并抛出一个CloneNotSupportedException(不支持克隆异常),就象第12章介绍的那样。亦可重载clone(),并简单地返回this。那样做会造成一定的混淆,因为客户程序员可能错误地认为对象尚未克隆,仍然操纵的是原来的那个。
Java也允许通过克隆(`Clone`)方式来创建一个对象。在这个例子中,将类设为`final`可禁止克隆的发生。由于`Singleton`是从`Object`直接继承的,所以`clone()`方法会保持`protected`(受保护)属性,不能够使用它(强行使用会造成编译期错误)。然而,假如我们是从一个类结构中继承,那个结构已经重载了`clone()`方法,使其具有`public`属性,并实现了`Cloneable`,那么为了禁止克隆,需要重载`clone()`,并抛出一个`CloneNotSupportedException`(不支持克隆异常),就象第12章介绍的那样。亦可重载`clone()`,并简单地返回`this`。那样做会造成一定的混淆,因为客户程序员可能错误地认为对象尚未克隆,仍然操纵的是原来的那个。
注意我们并不限于只能创建一个对象。亦可利用该技术创建一个有限的对象池。但在那种情况下,可能需要解决池内对象的共享问题。如果不幸真的遇到这个问题,可以自己设计一套方案,实现共享对象的登记与撤消登记。
16.1.2 模式分类
Design Patterns》一书讨论了23种不同的模式,并依据三个标准分类(所有标准都涉及那些可能发生变化的方面)。这三个标准是:
设计模式》一书讨论了23种不同的模式,并依据三个标准分类(所有标准都涉及那些可能发生变化的方面)。这三个标准是:
(1) 创建:对象的创建方式。这通常涉及对象创建细节的隔离,这样便不必依赖具体类型的对象,所以在新添一种对象类型时也不必改动代码。
(2) 结构:设计对象,满足特定的项目限制。这涉及对象与其他对象的连接方式,以保证系统内的改变不会影响到这些连接。
(3) 行为:对程序中特定类型的行动进行操纵的对象。这要求我们将希望采取的操作封装起来,比如解释一种语言、实现一个请求、在一个序列中遍历(就象在迭代器中那样)或者实现一种算法。本章提供了“观察器”(Observer)和“访问器”(Visitor)的模式的例子。
(3) 行为:对程序中特定类型的行动进行操纵的对象。这要求我们将希望采取的操作封装起来,比如解释一种语言、实现一个请求、在一个序列中遍历(就象在迭代器中那样)或者实现一种算法。本章提供了“观察器”(`Observer`)和“访问器”(`Visitor`)的模式的例子。
Design Patterns》为所有这23种模式都分别使用了一节,随附的还有大量示例,但大多是用C++编写的,少数用Smalltalk编写(如看过这本书,就知道这实际并不是个大问题,因为很容易即可将基本概念从两种语言翻译到Java里)。现在这本书并不打算重复《Design Patterns》介绍的所有模式,因为那是一本独立的书,大家应该单独阅读。相反,本章只准备给出一些例子,让大家先对模式有个大致的印象,并理解它们的重要性到底在哪里。
设计模式》为所有这23种模式都分别使用了一节,随附的还有大量示例,但大多是用C++编写的,少数用Smalltalk编写(如看过这本书,就知道这实际并不是个大问题,因为很容易即可将基本概念从两种语言翻译到Java里)。现在这本书并不打算重复《设计模式》介绍的所有模式,因为那是一本独立的书,大家应该单独阅读。相反,本章只准备给出一些例子,让大家先对模式有个大致的印象,并理解它们的重要性到底在哪里。
# 16.2 观察器模式
观察器(Observer)模式解决的是一个相当普通的问题:由于某些对象的状态发生了改变,所以一组对象都需要更新,那么该如何解决?在Smalltalk的MVC(模型-视图-控制器)的“模型-视图”部分中,或在几乎等价的“文档-视图结构”中,大家可以看到这个问题。现在我们有一些数据(“文档”)以及多个视图,假定为一张图(Plot)和一个文本视图。若改变了数据,两个视图必须知道对自己进行更新,而那正是“观察器”要负责的工作。这是一种十分常见的问题,它的解决方案已包括进标准的java.util库中。
观察器(`Observer`)模式解决的是一个相当普通的问题:由于某些对象的状态发生了改变,所以一组对象都需要更新,那么该如何解决?在Smalltalk的MVC(模型-视图-控制器)的“模型-视图”部分中,或在几乎等价的“文档-视图结构”中,大家可以看到这个问题。现在我们有一些数据(“文档”)以及多个视图,假定为一张图(`Plot`)和一个文本视图。若改变了数据,两个视图必须知道对自己进行更新,而那正是“观察器”要负责的工作。这是一种十分常见的问题,它的解决方案已包括进标准的`java.util`库中。
在Java中,有两种类型的对象用来实现观察器模式。其中,Observable类用于跟踪那些当发生一个改变时希望收到通知的所有个体——无论“状态”是否改变。如果有人说“好了,所有人都要检查自己,并可能要进行更新”,那么Observable类会执行这个任务——为列表中的每个“人”都调用notifyObservers()方法。notifyObservers()方法属于基类Observable的一部分。
在Java中,有两种类型的对象用来实现观察器模式。其中,`Observable`类用于跟踪那些当发生一个改变时希望收到通知的所有个体——无论“状态”是否改变。如果有人说“好了,所有人都要检查自己,并可能要进行更新”,那么`Observable`类会执行这个任务——为列表中的每个“人”都调用`notifyObservers()`方法。`notifyObservers()`方法属于基类`Observable`的一部分。
在观察器模式中,实际有两个方面可能发生变化:观察对象的数量以及更新的方式。也就是说,观察器模式允许我们同时修改这两个方面,不会干扰围绕在它周围的其他代码。
下面这个例子类似于第14章的ColorBoxes示例。箱子(Boxes)置于一个屏幕网格中,每个都初始化一种随机的颜色。此外,每个箱子都“实现”(implement)了“观察器”(Observer)接口,而且随一个Observable对象进行了注册。若点击一个箱子,其他所有箱子都会收到一个通知,指出一个改变已经发生。这是由于Observable对象会自动调用每个Observer对象的update()方法。在这个方法内,箱子会检查被点中的那个箱子是否与自己紧邻。若答案是肯定的,那么也修改自己的颜色,保持与点中那个箱子的协调。
下面这个例子类似于第14章的`ColorBoxes`示例。箱子(`Boxes`)置于一个屏幕网格中,每个都初始化一种随机的颜色。此外,每个箱子都“实现”(`implement`)了“观察器”(`Observer`)接口,而且随一个`Observable`对象进行了注册。若点击一个箱子,其他所有箱子都会收到一个通知,指出一个改变已经发生。这是由于`Observable`对象会自动调用每个`Observer`对象的`update()`方法。在这个方法内,箱子会检查被点中的那个箱子是否与自己紧邻。若答案是肯定的,那么也修改自己的颜色,保持与点中那个箱子的协调。
```
//: BoxObserver.java
......@@ -54,12 +54,12 @@ class OCBox extends Canvas implements Observer {
Observable notifier;
int x, y; // Locations in grid
Color cColor = newColor();
static final Color[] colors = {
Color.black, Color.blue, Color.cyan,
static final Color[] colors = {
Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green,
Color.lightGray, Color.magenta,
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow
Color.lightGray, Color.magenta,
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow
};
static final Color newColor() {
return colors[
......@@ -91,14 +91,14 @@ class OCBox extends Canvas implements Observer {
}
}
private final boolean nextTo(OCBox b) {
return Math.abs(x - b.x) <= 1 &&
return Math.abs(x - b.x) <= 1 &&
Math.abs(y - b.y) <= 1;
}
} ///:~
```
如果是首次查阅Observable的联机帮助文档,可能会多少感到有些困惑,因为它似乎表明可以用一个原始的Observable对象来管理更新。但这种说法是不成立的;大家可自己试试——在BoxObserver中,创建一个Observable对象,替换BoxObservable对象,看看会有什么事情发生。事实上,什么事情也不会发生。为真正产生效果,必须从Observable继承,并在派生类代码的某个地方调用setChanged()。这个方法需要设置“changed”(已改变)标志,它意味着当我们调用notifyObservers()的时候,所有观察器事实上都会收到通知。在上面的例子中,setChanged()只是简单地在notifyObservers()中调用,大家可依据符合实际情况的任何标准决定何时调用setChanged()
如果是首次查阅`Observable`的联机帮助文档,可能会多少感到有些困惑,因为它似乎表明可以用一个原始的`Observable`对象来管理更新。但这种说法是不成立的;大家可自己试试——在`BoxObserver`中,创建一个`Observable`对象,替换`BoxObservable`对象,看看会有什么事情发生。事实上,什么事情也不会发生。为真正产生效果,必须从`Observable`继承,并在派生类代码的某个地方调用`setChanged()`。这个方法需要设置`changed`(已改变)标志,它意味着当我们调用`notifyObservers()`的时候,所有观察器事实上都会收到通知。在上面的例子中,`setChanged()`只是简单地在`notifyObservers()`中调用,大家可依据符合实际情况的任何标准决定何时调用`setChanged()`
BoxObserver包含了单个Observable对象,名为notifier。每次创建一个OCBox对象时,它都会同notifier联系到一起。在OCBox中,只要点击鼠标,就会发出对notifyObservers()方法的调用,并将被点中的那个对象作为一个参数传递进去,使收到消息(用它们的update()方法)的所有箱子都能知道谁被点中了,并据此判断自己是否也要变动。通过notifyObservers()和update()中的代码的结合,我们可以应付一些非常复杂的局面。
`BoxObserver`包含了单个`Observable`对象,名为`notifier`。每次创建一个`OCBox`对象时,它都会同`notifier`联系到一起。在`OCBox`中,只要点击鼠标,就会发出对`notifyObservers()`方法的调用,并将被点中的那个对象作为一个参数传递进去,使收到消息(用它们的`update()`方法)的所有箱子都能知道谁被点中了,并据此判断自己是否也要变动。通过`notifyObservers()``update()`中的代码的结合,我们可以应付一些非常复杂的局面。
notifyObservers()方法中,表面上似乎观察器收到通知的方式必须在编译期间固定下来。然而,只要稍微仔细研究一下上面的代码,就会发现BoxObserver或OCBox中唯一需要留意是否使用BoxObservable的地方就是创建Observable对象的时候——从那时开始,所有东西都会使用基本的Observable接口。这意味着以后若想更改通知方式,可以继承其他Observable类,并在运行期间交换它们。
`notifyObservers()`方法中,表面上似乎观察器收到通知的方式必须在编译期间固定下来。然而,只要稍微仔细研究一下上面的代码,就会发现`BoxObserver``OCBox`中唯一需要留意是否使用`BoxObservable`的地方就是创建`Observable`对象的时候——从那时开始,所有东西都会使用基本的`Observable`接口。这意味着以后若想更改通知方式,可以继承其他`Observable`类,并在运行期间交换它们。
......@@ -5,7 +5,7 @@
这并不是一种普通的设计,因为它增加了一个新的限制。正是这个限制使问题变得非常有趣——它更象我们在工作中碰到的那些非常麻烦的问题。这个额外的限制是:垃圾抵达垃圾回收站时,它们全都是混合在一起的。程序必须为那些垃圾的分类定出一个模型。这正是RTTI发挥作用的地方:我们有大量不知名的垃圾,程序将正确判断出它们所属的类型。
```
//: RecycleA.java
//: RecycleA.java
// Recycling with RTTI
package c16.recyclea;
import java.util.*;
......@@ -82,7 +82,7 @@ public class RecycleA {
bin.addElement(new
Glass(Math.random() * 100));
}
Vector
Vector
glassBin = new Vector(),
paperBin = new Vector(),
alBin = new Vector();
......@@ -106,21 +106,21 @@ public class RecycleA {
} ///:~
```
要注意的第一个地方是package语句:
要注意的第一个地方是`package`语句:
```
package c16.recyclea;
```
这意味着在本书采用的源码目录中,这个文件会被置入从c16(代表第16章的程序)分支出来的recyclea子目录中。第17章的解包工具会负责将其置入正确的子目录。之所以要这样做,是因为本章会多次改写这个特定的例子;它的每个版本都会置入自己的“包”(package)内,避免类名的冲突。
这意味着在本书采用的源码目录中,这个文件会被置入从`c16`(代表第16章的程序)分支出来的`recyclea`子目录中。第17章的解包工具会负责将其置入正确的子目录。之所以要这样做,是因为本章会多次改写这个特定的例子;它的每个版本都会置入自己的“包”(`package`)内,避免类名的冲突。
其中创建了几个Vector对象,用于容纳Trash引用。当然,Vector实际容纳的是Object(对象),所以它们最终能够容纳任何东西。之所以要它们容纳Trash(或者从Trash派生出来的其他东西),唯一的理由是我们需要谨慎地避免放入除Trash以外的其他任何东西。如果真的把某些“错误”的东西置入Vector,那么不会在编译期得到出错或警告提示——只能通过运行期的一个异常知道自己已经犯了错误。
其中创建了几个`Vector`对象,用于容纳`Trash`引用。当然,`Vector`实际容纳的是`Object`(对象),所以它们最终能够容纳任何东西。之所以要它们容纳`Trash`(或者从`Trash`派生出来的其他东西),唯一的理由是我们需要谨慎地避免放入除`Trash`以外的其他任何东西。如果真的把某些“错误”的东西置入`Vector`,那么不会在编译期得到出错或警告提示——只能通过运行期的一个异常知道自己已经犯了错误。
Trash引用加入后,它们会丢失自己的特定标识信息,只会成为简单的Object引用(向上转换)。然而,由于存在多态性的因素,所以在我们通过Enumeration sorter调用动态绑定方法时,一旦结果Object已经转换回Trash,仍然会发生正确的行为。sumValue()也用一个Enumeration对Vector中的每个对象进行操作。
`Trash`引用加入后,它们会丢失自己的特定标识信息,只会成为简单的`Object`引用(向上转换)。然而,由于存在多态性的因素,所以在我们通过`Enumeration sorter`调用动态绑定方法时,一旦结果`Object`已经转换回`Trash`,仍然会发生正确的行为。`sumValue()`也用一个`Enumeration``Vector`中的每个对象进行操作。
表面上持,先把Trash的类型向上转换到一个集合容纳基类型的引用,再回过头重新向下转换,这似乎是一种非常愚蠢的做法。为什么不只是一开始就将垃圾置入适当的容器里呢?(事实上,这正是拨开“回收”一团迷雾的关键)。在这个程序中,我们很容易就可以换成这种做法,但在某些情况下,系统的结构及灵活性都能从向下转换中得到极大的好处。
表面上持,先把`Trash`的类型向上转换到一个集合容纳基类型的引用,再回过头重新向下转换,这似乎是一种非常愚蠢的做法。为什么不只是一开始就将垃圾置入适当的容器里呢?(事实上,这正是拨开“回收”一团迷雾的关键)。在这个程序中,我们很容易就可以换成这种做法,但在某些情况下,系统的结构及灵活性都能从向下转换中得到极大的好处。
该程序已满足了设计的初衷:它能够正常工作!只要这是个一次性的方案,就会显得非常出色。但是,真正有用的程序应该能够在任
何时候解决问题。所以必须问自己这样一个问题:“如果情况发生了变化,它还能工作吗?”举个例子来说,厚纸板现在是一种非常有价值的可回收物品,那么如何把它集成到系统中呢(特别是程序很大很复杂的时候)?由于前面在switch语句中的类型检查编码可能散布于整个程序,所以每次加入一种新类型时,都必须找到所有那些编码。若不慎遗漏一个,编译器除了指出存在一个错误之外,不能再提供任何有价值的帮助。
何时候解决问题。所以必须问自己这样一个问题:“如果情况发生了变化,它还能工作吗?”举个例子来说,厚纸板现在是一种非常有价值的可回收物品,那么如何把它集成到系统中呢(特别是程序很大很复杂的时候)?由于前面在`switch`语句中的类型检查编码可能散布于整个程序,所以每次加入一种新类型时,都必须找到所有那些编码。若不慎遗漏一个,编译器除了指出存在一个错误之外,不能再提供任何有价值的帮助。
RTTI在这里使用不当的关键是“每种类型都进行了测试”。如果由于类型的子集需要特殊的对待,所以只寻找那个子集,那么情况就会变得好一些。但假如在一个switch语句中查找每一种类型,那么很可能错过一个重点,使最终的代码很难维护。在下一节中,大家会学习如何逐步对这个程序进行改进,使其显得越来越灵活。这是在程序设计中一种非常有意义的例子。
RTTI在这里使用不当的关键是“每种类型都进行了测试”。如果由于类型的子集需要特殊的对待,所以只寻找那个子集,那么情况就会变得好一些。但假如在一个`switch`语句中查找每一种类型,那么很可能错过一个重点,使最终的代码很难维护。在下一节中,大家会学习如何逐步对这个程序进行改进,使其显得越来越灵活。这是在程序设计中一种非常有意义的例子。
# 16.4 改进设计
Design Patterns》书内所有方案的组织都围绕“程序进化时会发生什么变化”这个问题展开。对于任何设计来说,这都可能是最重要的一个问题。若根据对这个问题的回答来构造自己的系统,就可以得到两个方面的结果:系统不仅更易维护(而且更廉价),而且能产生一些能够重复使用的对象,进而使其他相关系统的构造也变得更廉价。这正是面向对象程序设计的优势所在,但这一优势并不是自动体现出来的。它要求对我们对需要解决的问题有全面而且深入的理解。在这一节中,我们准备在系统的逐步改进过程中向大家展示如何做到这一点。
设计模式》书内所有方案的组织都围绕“程序进化时会发生什么变化”这个问题展开。对于任何设计来说,这都可能是最重要的一个问题。若根据对这个问题的回答来构造自己的系统,就可以得到两个方面的结果:系统不仅更易维护(而且更廉价),而且能产生一些能够重复使用的对象,进而使其他相关系统的构造也变得更廉价。这正是面向对象程序设计的优势所在,但这一优势并不是自动体现出来的。它要求对我们对需要解决的问题有全面而且深入的理解。在这一节中,我们准备在系统的逐步改进过程中向大家展示如何做到这一点。
就目前这个回收系统来说,对“什么会变化”这个问题的回答是非常普通的:更多的类型会加入系统。因此,设计的目标就是尽可能简化这种类型的添加。在回收程序中,我们准备把涉及特定类型信息的所有地方都封装起来。这样一来(如果没有别的原因),所有变化对那些封装来说都是在本地进行的。这种处理方式也使代码剩余的部分显得特别清爽。
......@@ -9,7 +9,7 @@
这样便引出了面向对象程序设计时一条常规的准则,我最早是在Grady Booch那里听说的:“若设计过于复杂,就制作更多的对象”。尽管听起来有些暧昧,且简单得可笑,但这确实是我知道的最有用一条准则(大家以后会注意到“制作更多的对象”经常等同于“添加另一个层次的迂回”)。一般情况下,如果发现一个地方充斥着大量繁复的代码,就需要考虑什么类能使它显得清爽一些。用这种方式整理系统,往往会得到一个更好的结构,也使程序更加灵活。
首先考虑Trash对象首次创建的地方,这是main()里的一个switch语句:
首先考虑Trash对象首次创建的地方,这是`main()`里的一个`switch`语句:
```
for(int i = 0; i < 30; i++)
......@@ -28,10 +28,10 @@
}
```
这些代码显然“过于复杂”,也是新类型加入时必须改动代码的场所之一。如果经常都要加入新类型,那么更好的方案就是建立一个独立的方法,用它获取所有必需的信息,并创建一个引用,指向正确类型的一个对象——已经向上转换到一个Trash对象。在《Design Patterns》中,它被粗略地称呼为“创建模式”。要在这里应用的特殊模式是Factory方法的一种变体。在这里,Factory方法属于Trash的一名static(静态)成员。但更常见的一种情况是:它属于派生类中一个被重载的方法。
Factory方法的基本原理是我们将创建对象所需的基本信息传递给它,然后返回并等候引用(已经向上转换至基类型)作为返回值出现。从这时开始,就可以按多态性的方式对待对象了。因此,我们根本没必要知道所创建对象的准确类型是什么。事实上,Factory方法会把自己隐藏起来,我们是看不见它的。这样做可防止不慎的误用。如果想在没有多态性的前提下使用对象,必须明确地使用RTTI和指定转换。
这些代码显然“过于复杂”,也是新类型加入时必须改动代码的场所之一。如果经常都要加入新类型,那么更好的方案就是建立一个独立的方法,用它获取所有必需的信息,并创建一个引用,指向正确类型的一个对象——已经向上转换到一个`Trash`对象。在《设计模式》中,它被粗略地称呼为“创建模式”。要在这里应用的特殊模式是`Factory`方法的一种变体。在这里,`Factory`方法属于`Trash`的一名`static`(静态)成员。但更常见的一种情况是:它属于派生类中一个被重载的方法。
`Factory`方法的基本原理是我们将创建对象所需的基本信息传递给它,然后返回并等候引用(已经向上转换至基类型)作为返回值出现。从这时开始,就可以按多态性的方式对待对象了。因此,我们根本没必要知道所创建对象的准确类型是什么。事实上,`Factory`方法会把自己隐藏起来,我们是看不见它的。这样做可防止不慎的误用。如果想在没有多态性的前提下使用对象,必须明确地使用RTTI和指定转换。
但仍然存在一个小问题,特别是在基类中使用更复杂的方法(不是在这里展示的那种),且在派生类里重载(覆盖)了它的前提下。如果在派生类里请求的信息要求更多或者不同的参数,那么该怎么办呢?“创建更多的对象”解决了这个问题。为实现Factory方法,Trash类使用了一个新的方法,名为factory。为了将创建数据隐藏起来,我们用一个名为Info的新类包含factory方法创建适当的Trash对象时需要的全部信息。下面是Info一种简单的实现方式:
但仍然存在一个小问题,特别是在基类中使用更复杂的方法(不是在这里展示的那种),且在派生类里重载(覆盖)了它的前提下。如果在派生类里请求的信息要求更多或者不同的参数,那么该怎么办呢?“创建更多的对象”解决了这个问题。为实现`Factory`方法,`Trash`类使用了一个新的方法,名为`factory`。为了将创建数据隐藏起来,我们用一个名为`Info`的新类包含`factory`方法创建适当的`Trash`对象时需要的全部信息。下面是`Info`一种简单的实现方式:
```
class Info {
......@@ -46,9 +46,9 @@ class Info {
}
```
Info对象唯一的任务就是容纳用于factory()方法的信息。现在,假如出现了一种特殊情况,factory()需要更多或者不同的信息来新建一种类型的Trash对象,那么再也不需要改动factory()了。通过添加新的数据和构造器,我们可以修改Info类,或者采用子类处理更典型的面向对象形式。
`Info`对象唯一的任务就是容纳用于`factory()`方法的信息。现在,假如出现了一种特殊情况,`factory()`需要更多或者不同的信息来新建一种类型的`Trash`对象,那么再也不需要改动`factory()`了。通过添加新的数据和构造器,我们可以修改`Info`类,或者采用子类处理更典型的面向对象形式。
用于这个简单示例的factory()方法如下:
用于这个简单示例的`factory()`方法如下:
```
static Trash factory(Info i) {
......@@ -61,15 +61,15 @@ Info对象唯一的任务就是容纳用于factory()方法的信息。现在,
case 2:
return new Glass(i.data);
// Two lines here:
case 3:
case 3:
return new Cardboard(i.data);
}
}
```
在这里,对象的准确类型很容易即可判断出来。但我们可以设想一些更复杂的情况,factory()将采用一种复杂的算法。无论如何,现在的关键是它已隐藏到某个地方,而且我们在添加新类型时知道去那个地方。
在这里,对象的准确类型很容易即可判断出来。但我们可以设想一些更复杂的情况,`factory()`将采用一种复杂的算法。无论如何,现在的关键是它已隐藏到某个地方,而且我们在添加新类型时知道去那个地方。
新对象在main()中的创建现在变得非常简单和清爽:
新对象在`main()`中的创建现在变得非常简单和清爽:
```
for(int i = 0; i < 30; i++)
......@@ -80,21 +80,21 @@ Info对象唯一的任务就是容纳用于factory()方法的信息。现在,
Math.random() * 100)));
```
我们在这里创建了一个Info对象,用于将数据传入factory();后者在内存堆中创建某种Trash对象,并返回添加到Vector bin内的引用。当然,如果改变了参数的数量及类型,仍然需要修改这个语句。但假如Info对象的创建是自动进行的,也可以避免那个麻烦。例如,可将参数的一个Vector传递到Info对象的构造器中(或直接传入一个factory()调用)。这要求在运行期间对参数进行分析与检查,但确实提供了非常高的灵活程度。
我们在这里创建了一个`Info`对象,用于将数据传入`factory()`;后者在内存堆中创建某种T`rash`对象,并返回添加到`Vector bin`内的引用。当然,如果改变了参数的数量及类型,仍然需要修改这个语句。但假如`Info`对象的创建是自动进行的,也可以避免那个麻烦。例如,可将参数的一个`Vector`传递到`Info`对象的构造器中(或直接传入一个`factory()`调用)。这要求在运行期间对参数进行分析与检查,但确实提供了非常高的灵活程度。
大家从这个代码可看出Factory要负责解决的“领头变化”问题:如果向系统添加了新类型(发生了变化),唯一需要修改的代码在Factory内部,所以Factory将那种变化的影响隔离出来了。
大家从这个代码可看出`Factory`要负责解决的“领头变化”问题:如果向系统添加了新类型(发生了变化),唯一需要修改的代码在`Factory`内部,所以`Factory`将那种变化的影响隔离出来了。
16.4.2 用于原型创建的一个模式
上述设计模式的一个问题是仍然需要一个中心场所,必须在那里知道所有类型的对象:在factory()方法内部。如果经常都要向系统添加新类型,factory()方法为每种新类型都要修改一遍。若确实对这个问题感到苦恼,可试试再深入一步,将与类型有关的所有信息——包括它的创建过程——都移入代表那种类型的类内部。这样一来,每次新添一种类型的时候,需要做的唯一事情就是从一个类继承。
上述设计模式的一个问题是仍然需要一个中心场所,必须在那里知道所有类型的对象:在`factory()`方法内部。如果经常都要向系统添加新类型,`factory()`方法为每种新类型都要修改一遍。若确实对这个问题感到苦恼,可试试再深入一步,将与类型有关的所有信息——包括它的创建过程——都移入代表那种类型的类内部。这样一来,每次新添一种类型的时候,需要做的唯一事情就是从一个类继承。
为将涉及类型创建的信息移入特定类型的Trash里,必须使用“原型”(prototype)模式(来自《Design Patterns》那本书)。这里最基本的想法是我们有一个主控对象序列,为自己感兴趣的每种类型都制作一个。这个序列中的对象只能用于新对象的创建,采用的操作类似内建到Java根类Object内部的clone()机制。在这种情况下,我们将克隆方法命名为tClone()。准备创建一个新对象时,要事先收集好某种形式的信息,用它建立我们希望的对象类型。然后在主控序列中遍历,将手上的信息与主控序列中原型对象内任何适当的信息作对比。若找到一个符合自己需要的,就克隆它。
为将涉及类型创建的信息移入特定类型的Trash里,必须使用“原型”(`prototype`)模式(来自《设计模式》那本书)。这里最基本的想法是我们有一个主控对象序列,为自己感兴趣的每种类型都制作一个。这个序列中的对象只能用于新对象的创建,采用的操作类似内建到Java根类`Object`内部的`clone()`机制。在这种情况下,我们将克隆方法命名为`tClone()`。准备创建一个新对象时,要事先收集好某种形式的信息,用它建立我们希望的对象类型。然后在主控序列中遍历,将手上的信息与主控序列中原型对象内任何适当的信息作对比。若找到一个符合自己需要的,就克隆它。
采用这种方案,我们不必用硬编码的方式植入任何创建信息。每个对象都知道如何揭示出适当的信息,以及如何对自身进行克隆。所以一种新类型加入系统的时候,factory()方法不需要任何改变。
采用这种方案,我们不必用硬编码的方式植入任何创建信息。每个对象都知道如何揭示出适当的信息,以及如何对自身进行克隆。所以一种新类型加入系统的时候,`factory()`方法不需要任何改变。
为解决原型的创建问题,一个方法是添加大量方法,用它们支持新对象的创建。但在Java 1.1中,如果拥有指向Class对象的一个引用,那么它已经提供了对创建新对象的支持。利用Java 1.1的“反射”(已在第11章介绍)技术,即便我们只有指向Class对象的一个引用,亦可正常地调用一个构造器。这对原型问题的解决无疑是个完美的方案。
为解决原型的创建问题,一个方法是添加大量方法,用它们支持新对象的创建。但在Java 1.1中,如果拥有指向`Class`对象的一个引用,那么它已经提供了对创建新对象的支持。利用Java 1.1的“反射”(已在第11章介绍)技术,即便我们只有指向`Class`对象的一个引用,亦可正常地调用一个构造器。这对原型问题的解决无疑是个完美的方案。
原型列表将由指向所有想创建的Class对象的一个引用列表间接地表示。除此之外,假如原型处理失败,则factory()方法会认为由于一个特定的Class对象不在列表中,所以会尝试装载它。通过以这种方式动态装载原型,Trash类根本不需要知道自己要操纵的是什么类型。因此,在我们添加新类型时不需要作出任何形式的修改。于是,我们可在本章剩余的部分方便地重复利用它。
原型列表将由指向所有想创建的`Class`对象的一个引用列表间接地表示。除此之外,假如原型处理失败,则`factory()`方法会认为由于一个特定的`Class`对象不在列表中,所以会尝试装载它。通过以这种方式动态装载原型,`Trash`类根本不需要知道自己要操纵的是什么类型。因此,在我们添加新类型时不需要作出任何形式的修改。于是,我们可在本章剩余的部分方便地重复利用它。
```
//: Trash.java
......@@ -133,15 +133,15 @@ public abstract class Trash {
extends Exception {}
public static class CannotCreateTrashException
extends Exception {}
private static Vector trashTypes =
private static Vector trashTypes =
new Vector();
public static Trash factory(Info info)
throws PrototypeNotFoundException,
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 tc =
(Class)trashTypes.elementAt(i);
if (tc.getName().indexOf(info.id) != -1) {
try {
......@@ -150,7 +150,7 @@ public abstract class Trash {
Constructor ctor =
tc.getConstructor(
new Class[] {double.class});
// Call the constructor to create a
// Call the constructor to create a
// new object:
return (Trash)ctor.newInstance(
new Object[]{new Double(info.data)});
......@@ -170,7 +170,7 @@ public abstract class Trash {
e.printStackTrace();
throw new PrototypeNotFoundException();
}
// Loaded successfully. Recursive call
// Loaded successfully. Recursive call
// should work this time:
return factory(info);
}
......@@ -185,36 +185,36 @@ public abstract class Trash {
} ///:~
```
基本Trash类和sumValue()还是象往常一样。这个类剩下的部分支持原型模式。大家首先会看到两个内部类(被设为static属性,使其成为只为代码组织目的而存在的内部类),它们描述了可能出现的异常。在它后面跟随的是一个Vector trashTypes,用于容纳Class引用。
基本`Trash`类和`sumValue()`还是象往常一样。这个类剩下的部分支持原型模式。大家首先会看到两个内部类(被设为`static`属性,使其成为只为代码组织目的而存在的内部类),它们描述了可能出现的异常。在它后面跟随的是一个`Vector trashTypes`,用于容纳`Class`引用。
Trash.factory()中,Info对象id(Info类的另一个版本,与前面讨论的不同)内部的String包含了要创建的那种Trash的类型名称。这个String会与列表中的Class名比较。若存在相符的,那便是要创建的对象。当然,还有很多方法可以决定我们想创建的对象。之所以要采用这种方法,是因为从一个文件读入的信息可以转换成对象。
`Trash.factory()`中,`Info`对象`id``Info`类的另一个版本,与前面讨论的不同)内部的`String`包含了要创建的那种`Trash`的类型名称。这个`String`会与列表中的`Class`名比较。若存在相符的,那便是要创建的对象。当然,还有很多方法可以决定我们想创建的对象。之所以要采用这种方法,是因为从一个文件读入的信息可以转换成对象。
发现自己要创建的Trash(垃圾)种类后,接下来就轮到“反射”方法大显身手了。getConstructor()方法需要取得自己的参数——由Class引用构成的一个数组。这个数组代表着不同的参数,并按它们正确的顺序排列,以便我们查找的构造器使用。在这儿,该数组是用Java 1.1的数组创建语法动态创建的:
发现自己要创建的`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的语法创建的:
这个代码假定所有`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传递。幸运的是,这个问题只存在于基本数据类型中间。
在这种情况下,`double`必须置入一个封装(容器)类的内部,使其真正成为这个对象数组的一部分。通过调用`newInstance()`,会提取出`double`,但大家可能会觉得稍微有些迷惑——参数既可能是`double`,也可能是`Double`,但在调用的时候必须用`Double`传递。幸运的是,这个问题只存在于基本数据类型中间。
理解了具体的过程后,再来创建一个新对象,并且只为它提供一个Class引用,事情就变得非常简单了。就目前的情况来说,内部循环中的return永远不会执行,我们在终点就会退出。在这儿,程序动态装载Class对象,并把它加入trashTypes(垃圾类型)列表,从而试图纠正这个问题。若仍然找不到真正有问题的地方,同时装载又是成功的,那么就重复调用factory方法,重新试一遍。
理解了具体的过程后,再来创建一个新对象,并且只为它提供一个`Class`引用,事情就变得非常简单了。就目前的情况来说,内部循环中的`return`永远不会执行,我们在终点就会退出。在这儿,程序动态装载`Class`对象,并把它加入`trashTypes`(垃圾类型)列表,从而试图纠正这个问题。若仍然找不到真正有问题的地方,同时装载又是成功的,那么就重复调用`factory`方法,重新试一遍。
正如大家会看到的那样,这种设计模式最大的优点就是不需要改动代码。无论在什么情况下,它都能正常地使用(假定所有Trash子类都包含了一个构造器,用以获取单个double参数)。
正如大家会看到的那样,这种设计模式最大的优点就是不需要改动代码。无论在什么情况下,它都能正常地使用(假定所有`Trash`子类都包含了一个构造器,用以获取单个`double`参数)。
1. Trash子类
为了与原型机制相适应,对Trash每个新子类唯一的要求就是在其中包含了一个构造器,指示它获取一个double参数。Java 1.1的“反射”机制可负责剩下的所有工作。
下面是不同类型的Trash,每种类型都有它们自己的文件里,但都属于Trash包的一部分(同样地,为了方便在本章内重复使用):
为了与原型机制相适应,对`Trash`每个新子类唯一的要求就是在其中包含了一个构造器,指示它获取一个`double`参数。Java 1.1的“反射”机制可负责剩下的所有工作。
下面是不同类型的`Trash`,每种类型都有它们自己的文件里,但都属于`Trash`包的一部分(同样地,为了方便在本章内重复使用):
```
//: Aluminum.java
//: Aluminum.java
// The Aluminum class with prototyping
package c16.trash;
......@@ -228,10 +228,10 @@ public class Aluminum extends Trash {
} ///:~
```
下面是一种新的Trash类型:
下面是一种新的`Trash`类型:
```
//: Cardboard.java
//: Cardboard.java
// The Cardboard class with prototyping
package c16.trash;
......@@ -247,9 +247,9 @@ public class Cardboard extends Trash {
可以看出,除构造器以外,这些类根本没有什么特别的地方。
2. 从外部文件中解析出Trash
2. 从外部文件中解析出`Trash`
Trash对象有关的信息将从一个外部文件中读取。针对Trash的每个方面,文件内列出了所有必要的信息——每行都代表一个方面,采用“垃圾(废品)名称:值”的固定格式。例如:
`Trash`对象有关的信息将从一个外部文件中读取。针对`Trash`的每个方面,文件内列出了所有必要的信息——每行都代表一个方面,采用`垃圾(废品)名称:值`的固定格式。例如:
```
c16.Trash.Glass:54
......@@ -291,12 +291,12 @@ c16.Trash.Cardboard:22
注意在给定类名的时候,类路径必须包含在内,否则就找不到类。
为解析它,每一行内容都会读入,并用字符串方法indexOf()来建立“:”的一个索引。首先用字符串方法substring()取出垃圾的类型名称,接着用一个静态方法Double.valueOf()取得相应的值,并转换成一个double值。trim()方法则用于删除字符串两头的多余空格。
为解析它,每一行内容都会读入,并用字符串方法`indexOf()`来建立`:`的一个索引。首先用字符串方法`substring()`取出垃圾的类型名称,接着用一个静态方法`Double.valueOf()`取得相应的值,并转换成一个`double`值。`trim()`方法则用于删除字符串两头的多余空格。
Trash解析器置入单独的文件中,因为本章将不断地用到它。如下所示:
`Trash`解析器置入单独的文件中,因为本章将不断地用到它。如下所示:
```
//: ParseTrash.java
//: ParseTrash.java
// Open a file and parse its contents into
// Trash objects, placing each into a Vector
package c16.trash;
......@@ -304,7 +304,7 @@ import java.util.*;
import java.io.*;
public class ParseTrash {
public static void
public static void
fillBin(String filename, Fillable bin) {
try {
BufferedReader data =
......@@ -312,7 +312,7 @@ public class ParseTrash {
new FileReader(filename));
String buf;
while((buf = data.readLine())!= null) {
String type = buf.substring(0,
String type = buf.substring(0,
buf.indexOf(':')).trim();
double weight = Double.valueOf(
buf.substring(buf.indexOf(':') + 1)
......@@ -329,7 +329,7 @@ public class ParseTrash {
}
}
// Special case to handle Vector:
public static void
public static void
fillBin(String filename, Vector bin) {
fillBin(filename, new FillableVector(bin));
}
......@@ -337,10 +337,10 @@ public class ParseTrash {
```
RecycleA.java中,我们用一个Vector容纳Trash对象。然而,亦可考虑采用其他集合类型。为做到这一点,fillBin()的第一个版本将获取指向一个Fillable的引用。后者是一个接口,用于支持一个名为addTrash()的方法:
`RecycleA.java`中,我们用一个`Vector`容纳`Trash`对象。然而,亦可考虑采用其他集合类型。为做到这一点,`fillBin()`的第一个版本将获取指向一个`Fillable`的引用。后者是一个接口,用于支持一个名为`addTrash()`的方法:
```
//: Fillable.java
//: Fillable.java
// Any object that can be filled with Trash
package c16.trash;
......@@ -349,10 +349,10 @@ public interface Fillable {
} ///:~
```
支持该接口的所有东西都能伴随fillBin使用。当然,Vector并未实现Fillable,所以它不能工作。由于Vector将在大多数例子中应用,所以最好的做法是添加另一个重载的fillBin()方法,令其以一个Vector作为参数。利用一个适配器(Adapter)类,这个Vector可作为一个Fillable对象使用:
支持该接口的所有东西都能伴随`fillBin`使用。当然,`Vector`并未实现`Fillable`,所以它不能工作。由于`Vector`将在大多数例子中应用,所以最好的做法是添加另一个重载的`fillBin()`方法,令其以一个`Vector`作为参数。利用一个适配器(`Adapter`)类,这个`Vector`可作为一个`Fillable`对象使用:
```
//: FillableVector.java
//: FillableVector.java
// Adapter that makes a Vector Fillable
package c16.trash;
import java.util.*;
......@@ -366,23 +366,23 @@ public class FillableVector implements Fillable {
} ///:~
```
可以看到,这个类唯一的任务就是负责将Fillable的addTrash()同Vector的addElement()方法连接起来。利用这个类,已重载的fillBin()方法可在ParseTrash.java中伴随一个Vector使用:
可以看到,这个类唯一的任务就是负责将`Fillable``addTrash()``Vector``addElement()`方法连接起来。利用这个类,已重载的`fillBin()`方法可在`ParseTrash.java`中伴随一个`Vector`使用:
```
public static void
public static void
fillBin(String filename, Vector bin) {
fillBin(filename, new FillableVector(bin));
}
```
这种方案适用于任何频繁用到的集合类。除此以外,集合类还可提供它自己的适配器类,并实现Fillable(稍后即可看到,在DynaTrash.java中)。
这种方案适用于任何频繁用到的集合类。除此以外,集合类还可提供它自己的适配器类,并实现`Fillable`(稍后即可看到,在`DynaTrash.java`中)。
3. 原型机制的重复应用
现在,大家可以看到采用原型技术的、修订过的RecycleA.java版本了:
现在,大家可以看到采用原型技术的、修订过的`RecycleA.java`版本了:
```
//: RecycleAP.java
//: RecycleAP.java
// Recycling with RTTI and Prototypes
package c16.recycleap;
import c16.trash.*;
......@@ -393,7 +393,7 @@ public class RecycleAP {
Vector bin = new Vector();
// Fill up the Trash bin:
ParseTrash.fillBin("Trash.dat", bin);
Vector
Vector
glassBin = new Vector(),
paperBin = new Vector(),
alBin = new Vector();
......@@ -419,7 +419,7 @@ public class RecycleAP {
```
所有Trash对象——以及ParseTrash及支撑类——现在都成为名为c16.trash的一个包的一部分,所以它们可以简单地导入。
无论打开包含了Trash描述信息的数据文件,还是对那个文件进行解析,所有涉及到的操作均已封装到static(静态)方法ParseTrash.fillBin()里。所以它现在已经不是我们设计过程中要注意的一个重点。在本章剩余的部分,大家经常都会看到无论添加的是什么类型的新类,ParseTrash.fillBin()都会持续工作,不会发生改变,这无疑是一种优良的设计模式。
所有`Trash`对象——以及`ParseTrash`及支撑类——现在都成为名为`c16.trash`的一个包的一部分,所以它们可以简单地导入。
无论打开包含了`Trash`描述信息的数据文件,还是对那个文件进行解析,所有涉及到的操作均已封装到`static`(静态)方法`ParseTrash.fillBin()`里。所以它现在已经不是我们设计过程中要注意的一个重点。在本章剩余的部分,大家经常都会看到无论添加的是什么类型的新类,`ParseTrash.fillBin()`都会持续工作,不会发生改变,这无疑是一种优良的设计模式。
提到对象的创建,这一方案确实已将新类型加入系统所需的变动严格地“本地化”了。但在使用RTTI的过程中,却存在着一个严重的问题,这里已明确地显露出来。程序表面上工作得很好,但却永远侦测到不能“硬纸板”(Cardboard)这种新的废品类型——即使列表里确实有一个硬纸板类型!之所以会出现这种情况,完全是由于使用了RTTI的缘故。RTTI只会查找那些我们告诉它查找的东西。RTTI在这里错误的用法是“系统中的每种类型”都进行了测试,而不是仅测试一种类型或者一个类型子集。正如大家以后会看到的那样,在测试每一种类型时可换用其他方式来运用多态性特征。但假如以这种形式过多地使用RTTI,而且又在自己的系统里添加了一种新类型,很容易就会忘记在程序里作出适当的改动,从而埋下以后难以发现的Bug。因此,在这种情况下避免使用RTTI是很有必要的,这并不仅仅是为了表面好看——也是为了产生更易维护的代码。
提到对象的创建,这一方案确实已将新类型加入系统所需的变动严格地“本地化”了。但在使用RTTI的过程中,却存在着一个严重的问题,这里已明确地显露出来。程序表面上工作得很好,但却永远侦测到不能“硬纸板”(`Cardboard`)这种新的废品类型——即使列表里确实有一个硬纸板类型!之所以会出现这种情况,完全是由于使用了RTTI的缘故。RTTI只会查找那些我们告诉它查找的东西。RTTI在这里错误的用法是“系统中的每种类型”都进行了测试,而不是仅测试一种类型或者一个类型子集。正如大家以后会看到的那样,在测试每一种类型时可换用其他方式来运用多态性特征。但假如以这种形式过多地使用RTTI,而且又在自己的系统里添加了一种新类型,很容易就会忘记在程序里作出适当的改动,从而埋下以后难以发现的Bug。因此,在这种情况下避免使用RTTI是很有必要的,这并不仅仅是为了表面好看——也是为了产生更易维护的代码。
......@@ -8,7 +8,7 @@
设计模式要表明的观点是“OOP并不仅仅同多态性有关”。应当与OOP有关的是“将发生变化的东西同保持不变的东西分隔开来”。多态性是达到这一目的的特别重要的手段。而且假如编程语言直接支持多态性,那么它就显得尤其有用(由于直接支持,所以不必自己动手编写,从而节省大量的精力和时间)。但设计模式向我们揭示的却是达到基本目标的另一些常规途径。而且一旦熟悉并掌握了它的用法,就会发现自己可以做出更有创新性的设计。
由于《Design Patterns》这本书对程序员造成了如此重要的影响,所以他们纷纷开始寻找其他模式。随着的时间的推移,这类模式必然会越来越多。JimCoplien(http://www.bell-labs.com/~cope主页作者)向我们推荐了这样的一些站点,上面有许多很有价值的模式说明:
由于《设计模式》这本书对程序员造成了如此重要的影响,所以他们纷纷开始寻找其他模式。随着的时间的推移,这类模式必然会越来越多。JimCoplien(http://www.bell-labs.com/~cope主页作者)向我们推荐了这样的一些站点,上面有许多很有价值的模式说明:
http://st-www.cs.uiuc.edu/users/patterns
......
......@@ -2,7 +2,7 @@
本章要向大家介绍重要但却并不是那么传统的“模式”(Pattern)程序设计方法。
在向面向对象程序设计的演化过程中,或许最重要的一步就是“设计模式”(Design Pattern)的问世。它在由Gamma,Helm和Johnson编著的《Design Patterns》一书中被定义成一个“里程碑”(该书由Addison-Wesley于1995年出版,注释①)。那本书列出了解决这个问题的23种不同的方法。在本章中,我们准备伴随几个例子揭示出设计模式的基本概念。这或许能激起您阅读《Design Pattern》一书的欲望。事实上,那本书现在已成为几乎所有OOP程序员都必备的参考书。
在向面向对象程序设计的演化过程中,或许最重要的一步就是“设计模式”(Design Pattern)的问世。它在由Gamma,Helm和Johnson编著的《设计模式》一书中被定义成一个“里程碑”(该书由Addison-Wesley于1995年出版,注释①)。那本书列出了解决这个问题的23种不同的方法。在本章中,我们准备伴随几个例子揭示出设计模式的基本概念。这或许能激起您阅读《Design Pattern》一书的欲望。事实上,那本书现在已成为几乎所有OOP程序员都必备的参考书。
①:但警告大家:书中的例子是用C++写的。
......
......@@ -38,7 +38,7 @@ David Flanagan
出版时间:1997
简介:针对三种主要的Java ORB(Visbroker,Orbix,Joe),本书分别用大量代码实例进行了详尽的阐述。
■《Design Patterns
■《设计模式
作者:Gamma,Helm,Johnson和Vlissides
出版社:Addison-Wesley
出版时间:1995
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册