# 16.1 模式的概念
在最開始,可將模式想象成一種特別聰明、能夠自我適應的手法,它可以解決特定類型的問題。也就是說,它類似一些需要全面認識某個問題的人。在了解了問題的方方面面以后,最后提出一套最通用、最靈活的解決方案。具體問題或許是以前見到并解決過的。然而,從前的方案也許并不是最完善的,大家會看到它如何在一個模式里具體表達出來。
盡管我們稱之為“設計模式”,但它們實際上并不局限于設計領域。思考“模式”時,應脫離傳統意義上分析、設計以及實現的思考方式。相反,“模式”是在一個程序里具體表達一套完整的思想,所以它有時可能出現在分析階段或者高級設計階段。這一點是非常有趣的,因為模式具有以代碼形式直接實現的形式,所以可能不希望它在低級設計或者具體實現以前顯露出來(而且事實上,除非真正進入那些階段,否則一般意識不到自己需要一個模式來解決問題)。
模式的基本概念亦可看成是程序設計的基本概念:添加一層新的抽象!只要我們抽象了某些東西,就相當于隔離了特定的細節。而且這后面最引人注目的動機就是“將保持不變的東西身上發生的變化孤立出來”。這樣做的另一個原因是一旦發現程序的某部分由于這樣或那樣的原因可能發生變化,我們一般都想防止那些改變在代碼內部繁衍出其他變化。這樣做不僅可以降低代碼的維護代價,也更便于我們理解(結果同樣是降低開銷)。
為設計出功能強大且易于維護的應用項目,通常最困難的部分就是找出我稱之為“領頭變化”的東西。這意味著需要找出造成系統改變的最重要的東西,或者換一個角度,找出付出代價最高、開銷最大的那一部分。一旦發現了“領頭變化”,就可以為自己定下一個焦點,圍繞它展開自己的設計。
所以設計模式的最終目標就是將代碼中變化的內容隔離開。如果從這個角度觀察,就會發現本書實際已采用了一些設計模式。舉個例子來說,繼承可以想象成一種設計模式(類似一個由編譯器實現的)。在都擁有同樣接口(即保持不變的東西)的對象內部,它允許我們表達行為上的差異(即發生變化的東西)。組合亦可想象成一種模式,因為它允許我們修改——動態或靜態——用于實現類的對象,所以也能修改類的運作方式。
在《設計模式》一書中,大家還能看到另一種模式:“迭代器”(即`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.
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: compile-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 模式分類
《設計模式》一書討論了23種不同的模式,并依據三個標準分類(所有標準都涉及那些可能發生變化的方面)。這三個標準是:
(1) 創建:對象的創建方式。這通常涉及對象創建細節的隔離,這樣便不必依賴具體類型的對象,所以在新添一種對象類型時也不必改動代碼。
(2) 結構:設計對象,滿足特定的項目限制。這涉及對象與其他對象的連接方式,以保證系統內的改變不會影響到這些連接。
(3) 行為:對程序中特定類型的行動進行操縱的對象。這要求我們將希望采取的操作封裝起來,比如解釋一種語言、實現一個請求、在一個序列中遍歷(就象在迭代器中那樣)或者實現一種算法。本章提供了“觀察器”(`Observer`)和“訪問器”(`Visitor`)的模式的例子。
《設計模式》為所有這23種模式都分別使用了一節,隨附的還有大量示例,但大多是用C++編寫的,少數用Smalltalk編寫(如看過這本書,就知道這實際并不是個大問題,因為很容易即可將基本概念從兩種語言翻譯到Java里)。現在這本書并不打算重復《設計模式》介紹的所有模式,因為那是一本獨立的書,大家應該單獨閱讀。相反,本章只準備給出一些例子,讓大家先對模式有個大致的印象,并理解它們的重要性到底在哪里。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物