Java编程思想第4版[中文版](PDF格式)-第152部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
析、设计以及实施的思考方式。相反,“范式”是在一个程序里具体表达一套完整的思想,所以它有时可能
出现在分析阶段或者高级设计阶段。这一点是非常有趣的,因为范式具有以代码形式直接实现的形式,所以
可能不希望它在低级设计或者具体实施以前显露出来(而且事实上,除非真正进入那些阶段,否则一般意识
不到自己需要一个范式来解决问题)。
范式的基本概念亦可看成是程序设计的基本概念:添加一层新的抽象!只要我们抽象了某些东西,就相当于
隔离了特定的细节。而且这后面最引人注目的动机就是“将保持不变的东西身上发生的变化孤立出来”。这
样做的另一个原因是一旦发现程序的某部分由于这样或那样的原因可能发生变化,我们一般都想防止那些改
变在代码内部繁衍出其他变化。这样做不仅可以降低代码的维护代价,也更便于我们理解(结果同样是降低
开销)。
为设计出功能强大且易于维护的应用项目,通常最困难的部分就是找出我称之为“领头变化”的东西。这意
味着需要找出造成系统改变的最重要的东西,或者换一个角度,找出付出代价最高、开销最大的那一部分。
一旦发现了“领头变化”,就可以为自己定下一个焦点,围绕它展开自己的设计。
所以设计范式的最终目标就是将代码中变化的内容隔离开。如果从这个角度观察,就会发现本书实际已采用
了一些设计范式。举个例子来说,继承可以想象成一种设计范式(类似一个由编译器实现的)。在都拥有同
样接口(即保持不变的东西)的对象内部,它允许我们表达行为上的差异(即发生变化的东西)。合成亦可
想象成一种范式,因为它允许我们修改——动态或静态——用于实现类的对象,所以也能修改类的运作方
式。
在《Design Patterns》一书中,大家还能看到另一种范式:“继承器”(即Iterator,Java 1。0 和 1。1 不
负责任地把它叫作Enumeration,即“枚举”;Java1。2 的集合则改回了“继承器”的称呼)。当我们在集合
里遍历,逐个选择不同的元素时,继承器可将集合的实施细节有效地隐藏起来。利用继承器,可以编写出通
用的代码,以便对一个序列里的所有元素采取某种操作,同时不必关心这个序列是如何构建的。这样一来,
我们的通用代码即可伴随任何能产生继承器的集合使用。
16。1。1 单子
或许最简单的设计范式就是“单子”(Singleton),它能提供对象的一个(而且只有一个)实例。单子在
Java 库中得到了应用,但下面这个例子显得更直接一些:
//: SingletonPattern。java
// The Singleton design pattern: you can
// never instantiate more than one。
588
…………………………………………………………Page 590……………………………………………………………
package c16;
// Since this isn't inherited from a Cloneable
// base class and cloneability isn't added;
// making it final prevents cloneability from
// being added in any derived classes:
final class Singleton {
private static Singleton s = new Singleton(47);
private int i;
private Singleton(int x) { i = x; }
public static Singleton getHandle() {
return s;
}
public int getValue() { return i; }
public void setValue(int x) { i = x; }
}
public class SingletonPattern {
public static void main(String'' args) {
Singleton s = Singleton。getHandle();
System。out。println(s。getValue());
Singleton s2 = Singleton。getHandle();
s2。setValue(9);
System。out。println(s。getValue());
try {
// Can't do this: pile…time error。
// Singleton s3 = (Singleton)s2。clone();
} catch(Exception e) {}
}
} ///:~
创建单子的关键就是防止客户程序员采用除由我们提供的之外的任何一种方式来创建一个对象。必须将所有
构建器都设为private (私有),而且至少要创建一个构建器,以防止编译器帮我们自动同步一个默认构建
器(它会自做聪明地创建成为“友好的”——friendly,而非 private)。
此时应决定如何创建自己的对象。在这儿,我们选择了静态创建的方式。但亦可选择等候客户程序员发出一
个创建请求,然后根据他们的要求动态创建。不管在哪种情况下,对象都应该保存为“私有”属性。我们通
过公用方法提供访问途径。在这里,getHandle()会产生指向 Singleton 的一个句柄。剩下的接口
(getValue()和 setValue())属于普通的类接口。
Java 也允许通过克隆(Clone)方式来创建一个对象。在这个例子中,将类设为 final 可禁止克隆的发生。
由于Singleton 是从Object 直接继承的,所以 clone()方法会保持 protected (受保护)属性,不能够使用
它(强行使用会造成编译期错误)。然而,假如我们是从一个类结构中继承,那个结构已经过载了clone()
方法,使其具有public 属性,并实现了Cloneable ,那么为了禁止克隆,需要过载clone(),并掷出一个
CloneNotSupportedException (不支持克隆违例),就象第12章介绍的那样。亦可过载 clone(),并简单地
返回 this。那样做会造成一定的混淆,因为客户程序员可能错误地认为对象尚未克隆,仍然操纵的是原来的
那个。
注意我们并不限于只能创建一个对象。亦可利用该技术创建一个有限的对象池。但在那种情况下,可能需要
解决池内对象的共享问题。如果不幸真的遇到这个问题,可以自己设计一套方案,实现共享对象的登记与撤
消登记。
16。1。2 范式分类
《Design Patterns》一书讨论了23 种不同的范式,并依据三个标准分类(所有标准都涉及那些可能发生变
化的方面)。这三个标准是:
589
…………………………………………………………Page 591……………………………………………………………
(1) 创建:对象的创建方式。这通常涉及对象创建细节的隔离,这样便不必依赖具体类型的对象,所以在新
添一种对象类型时也不必改动代码。
(2) 结构:设计对象,满足特定的项目限制。这涉及对象与其他对象的连接方式,以保证系统内的改变不会
影响到这些连接。
(3) 行为:对程序中特定类型的行动进行操纵的对象。这要求我们将希望采取的操作封装起来,比如解释一
种语言、实现一个请求、在一个序列中遍历(就象在继承器中那样)或者实现一种算法。本章提供了“观察
器”(Observer)和“访问器”(Visitor)的范式的例子。
《Design Patterns》为所有这 23 种范式都分别使用了一节,随附的还有大量示例,但大多是用 C++编写
的,少数用 Smalltalk 编写(如看过这本书,就知道这实际并不是个大问题,因为很容易即可将基本概念从
两种语言翻译到Java 里)。现在这本书并不打算重复《Design Patterns》介绍的所有范式,因为那是一本
独立的书,大家应该单独阅读。相反,本章只准备给出一些例子,让大家先对范式有个大致的印象,并理解
它们的重要性到底在哪里。
16。2 观察器范式
观察器(Observer )范式解决的是一个相当普通的问题:由于某些对象的状态发生了改变,所以一组对象都
需要更新,那么该如何解决?在Smalltalk 的MVC (模型-视图-控制器)的“模型-视图”部分中,或在
几乎等价的“文档-视图结构”中,大家可以看到这个问题。现在我们有一些数据(“文档”)以及多个视
图,假定为一张图(Plot )和一个文本视图。若改变了数据,两个视图必须知道对自己进行更新,而那正是
“观察器”要负责的工作。这是一种十分常见的问题,它的解决方案已包括进标准的java。util 库中。
在Java 中,有两种类型的对象用来实现观察器范式。其中,Observable 类用于跟踪那些当发生一个改变时
希望收到通知的所有个体——无论“状态”是否改变。如果有人说“好了,所有人都要检查自己,并可能要
进行更新”,那么 Observable 类会执行这个任务——为列表中的每个“人”都调用 notifyObservers()方
法。notifyObservers()方法属于基础类Observable 的一部分。
在观察器范式中,实际有两个方面可能发生变化:观察对象的数量以及更新的方式。也就是说,观察器范式
允许我们同时修改这两个方面,不会干扰围绕在它周围的其他代码。
下面这个例子类似于第 14章的ColorBoxes 示例。箱子(Boxes)置于一个屏幕网格中,每个都初始化一种随
机的颜色。此外,每个箱子都“实现”(implement)了“观察器”(Observer )接口,而且随一个
Observable 对象进行了注册。若点击一个箱子,其他所有箱子都会收到一个通知,指出一个改变已经发生。
这是由于Observable 对象会自动调用每个Observer 对象的 update()方法。在这个方法内,箱子会检查被点
中的那个箱子是否与自己紧邻。若答案是肯定的,那么也修改自己的颜色,保持与点中那个箱子的协调。
//: BoxObserver。java
// Demonstration of Observer pattern using
// Java's built…in observer classes。
import java。awt。*;
import java。awt。event。*;
import java。util。*;
// You must inherit a new type of Observable:
class BoxObservable extends Observable {
public void notifyObservers(Object b) {
// Otherwise it won't propagate changes:
setChanged();
super。notifyObservers(b);
}
}
public class BoxObserver extends Frame {
Observable notifier = new BoxObservable ();
public BoxObserver(int grid) {
setTitle(〃Demonstrates Observer pattern〃);
590
…………………………………………………………Page 592……………………………………………………………
setLayout(new GridLayout(grid; grid));
for(int x = 0; x 《 grid; x++)
for(int y = 0; y 《 grid; y++)
add(new OCBox(x; y; notifier));
}
public static void main(String'' args) {
int grid = 8;
if(args。length 》 0)
grid = Integer。parseInt(args'0');
Frame f = new BoxObserver(grid);
f。setSize(500; 400);
f。setVisible(true);
f。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
}
}
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;
Color。darkGray; Color。gray; Color。green;
Color。lightGray; Color。magenta;
Color。orange; Color。pink; Color。red;
Color。white; Color。yellow
};
static final Color newColor() {
return colors'
(int)(Math。random() * colors。length)
';
}
OCBox(int x; int y; Observable notifier) {
this。x = x;
this。y = y;
notifier。addObserver(this);
this。notifier = notifier;
addMouseListener(new ML());
}
public void paint(Graphics g) {
g。setColor(cColor);
Dimension s = getSize();
g。fillRect(0; 0; s。width; s。height);
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
notifier。notifyObservers(OCBox。this);
591
…………………………………………………………Page 593……………………………………………………………
}
}
public void update(Observable o; Object arg) {
OCBox clic