設計模式(Patterns in Java)

設計模式(Patterns in Java) -- http://www.jdon.com
1
設計模式(Patterns in Java)
Java 提供了豐富的API,同時又有強大的數據庫系統作底層支持,那麼我們的編程似乎
變成了類似積木的簡單"拼湊"和調用,甚至有人提倡"藍領程序員",這些都是對現代編
程技術的不瞭解所至.
在真正可複用的面向對象編程中,GoF 的《設計模式》爲我們提供了一套可複用的面向對
象技術,再配合Refactoring(重構方法),所以很少存在簡單重複的工作,加上Java 代碼
的精煉性和麪向對象純潔性(設計模式是java 的靈魂),編程工作將變成一個讓你時刻體
驗創造快感的激動人心的過程.
爲能和大家能共同探討"設計模式",我將自己在學習中的心得寫下來,只是想幫助更多
人更容易理解GoF 的《設計模式》。由於原著都是以C++爲例, 以Java 爲例的設計模式
基本又都以圖形應用爲例,而我們更關心Java 在中間件等服務器方面的應用,因此,本站
所有實例都是非圖形應用,並且順帶剖析Jive 論壇系統.同時爲降低理解難度,儘量避
免使用UML 圖.
如果你有一定的面向對象編程經驗,你會發現其中某些設計模式你已經無意識的使用過
了;如果你是一個新手,那麼從開始就培養自己良好的編程習慣(讓你的的程序使用通用
的模式,便於他人理解;讓你自己減少重複性的編程工作),這無疑是成爲一個優秀程序
員的必備條件.
整個設計模式貫穿一個原理:面對接口編程,而不是面對實現.目標原則是:降低耦合,增
強靈活性.
1:前言
學習GoF 設計模式的重要性
建築和軟件中模式之異同
2:GoF 設計模式
A.創建模式
設計模式之Factory(工廠方法和抽象工廠)
使用工廠模式就象使用new 一樣頻繁.
設計模式之Prototype(原型)
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的
對象。
設計模式(Patterns in Java) -- http://www.jdon.com
2
設計模式之Builder
汽車由車輪方向盤發動機很多部件組成,同時,將這些部件組裝
成汽車也是一件複雜的工作,Builder 模式就是將這兩種情況分開
進行。
設計模式之Singleton(單態)
保證一個類只有一個實例,並提供一個訪問它的全局訪問點
B.結構模式
設計模式之Facade
可擴展的使用JDBC 針對不同的數據庫編程,Facade 提供了一種靈
活的實現.
設計模式之Proxy
以Jive 爲例,剖析代理模式在用戶級別授權機制上的應用
設計模式之Adapter
使用類再生的兩個方式:組合(new)和繼承(extends),這個已經在
"thinking in java"中提到過.
設計模式之Composite
就是將類用樹形結構組合成一個單位.你向別人介紹你是某單位,
你是單位中的一個元素,別人和你做買賣,相當於和單位做買賣。
文章中還對Jive 再進行了剖析。
設計模式之Decorator
Decorator 是個油漆工,給你的東東的外表刷上美麗的顏色.
設計模式之Bridge
將"牛郎織女"分開(本應在一起,分開他們,形成兩個接口),在他們之
間搭建一個橋(動態的結合)
設計模式之Flyweight
提供Java 運行性能,降低小而大量重複的類的開銷.
C.行爲模式
設計模式之Template
實際上向你介紹了爲什麼要使用Java 抽象類,該模式原理簡單,使
用很普遍.
設計模式之Memento
很簡單一個模式,就是在內存中保留原來數據的拷貝.
設計模式之Observer
介紹如何使用Java API 提供的現成Observer
設計模式(Patterns in Java) -- http://www.jdon.com
3
設計模式之Chain of Responsibility
各司其職的類串成一串,好象擊鼓傳花,當然如果自己能完成,就不
要推委給下一個.
設計模式之Command
什麼是將行爲封裝,Command 是最好的說明.
設計模式之State
狀態是編程中經常碰到的實例,將狀態對象化,設立狀態變換器,便
可在狀態中輕鬆切換.
設計模式之Strategy
不同算法各自封裝,用戶端可隨意挑選需要的算法.
設計模式之Mediator
Mediator 很象十字路口的紅綠燈,每個車輛只需和紅綠燈交互就可
以.
設計模式之Interpreter
主要用來對語言的分析,應用機會不多.
設計模式之Visitor
訪問者在進行訪問時,完成一系列實質性操作,而且還可以擴展.
設計模式之Iterator
這個模式已經被整合入Java 的Collection.在大多數場合下無需自
己製造一個Iterator,只要將對象裝入Collection中,直接使用Iterator
進行對象遍歷。
3:英文資料
Thinking in Patterns with Java Thinking in Java 的作者Eckel 又一
著作!
CMSC491D Design Patterns In Java
Overview of Design Patterns 精確定義各個模式以及他們的關係
Design Patterns Java Companion
EJB 設計模式(英文) 從設計模式去理解EJB 或J2EE我認爲是個非常有效
的辦法.
設計模式(Patterns in Java) -- http://www.jdon.com
4
學習GoF 設計模式的重要性
GoF 的《設計模式》也許你沒有聽說過,但是《Thingking in Java》(Java 編程思想)你應該
知道甚至讀過吧!
在瀏覽《Thingking in Java》(第一版)時,你是不是覺得好象這還是一本Java 基礎語言書
籍?但又不純粹是,因爲這本書的作者將面向對象的思想巧妙的融合在Java 的具體技術上,
潛移默化的讓你感覺到了一種新的語言和新的思想方式的誕生。
但是讀完這本書,你對書中這些蘊含的思想也許需要一種更明晰更系統更透徹的瞭解和掌
握,那麼你就需要研讀GoF 的《設計模式》了。
《Thingking in Java》(第一版中文)是這樣描述設計模式的:他在由Gamma, Helm 和Johnson
Vlissides 簡稱Gang of Four(四人幫),縮寫GoF 編著的《Design Patterns》一書中被定義
成一個“里程碑”。事實上,那本書現在已成爲幾乎所有OOP(面向對象程序設計)程序員都
必備的參考書。(在國外是如此)。
GoF 的《設計模式》是所有面嚮對象語言(C++ Java C#)的基礎,只不過不同的語言將之實
現得更方便地使用。
GOF 的設計模式是一座"橋"
就Java 語言體系來說,GOF 的設計模式是Java 基礎知識和J2EE 框架知識之間一座隱性的"
橋"。
會Java 的人越來越多,但是一直徘徊在語言層次的程序員不在少數,真正掌握Java 中接口
或抽象類的應用不是很多,大家經常以那些技術只適合大型項目爲由,避開或忽略它們,實
際中,Java 的接口或抽象類是真正體現Java 思想的核心所在,這些你都將在GoF 的設計模
式裏領略到它們變幻無窮的魔力。
GoF 的設計模式表面上好象也是一種具體的"技術",而且新的設計模式不斷在出現,設計模
式自有其自己的發展軌道,而這些好象和J2EE .Net 等技術也無關!
實際上,GoF 的設計模式並不是一種具體"技術",它講述的是思想,它不僅僅展示了接口或
抽象類在實際案例中的靈活應用和智慧,讓你能夠真正掌握接口或抽象類的應用,從而在原
來的Java 語言基礎上躍進一步,更重要的是,GoF 的設計模式反覆向你強調一個宗旨:要
讓你的程序儘可能的可重用。
這其實在向一個極限挑戰:軟件需求變幻無窮,計劃沒有變化快,但是我們還是要尋找出不
變的東西,並將它和變化的東西分離開來,這需要非常的智慧和經驗。
而GoF 的設計模式是在這方面開始探索的一塊里程碑。
設計模式(Patterns in Java) -- http://www.jdon.com
5
J2EE 等屬於一種框架軟件,什麼是框架軟件?它不同於我們以前接觸的Java API 等,那些
屬於Toolkist(工具箱),它不再被動的被使用,被調用,而是深刻的介入到一個領域中去,
J2EE 等框架軟件設計的目的是將一個領域中不變的東西先定義好,比如整體結構和一些主
要職責(如數據庫操作事務跟蹤安全等),剩餘的就是變化的東西,針對這個領域中具體應
用產生的具體不同的變化需求,而這些變化東西就是J2EE 程序員所要做的。
由此可見,設計模式和J2EE 在思想和動機上是一脈相承,只不過
1.設計模式更抽象,J2EE 是具體的產品代碼,我們可以接觸到,而設計模式在對每個應用
時纔會產生具體代碼。
2.設計模式是比J2EE 等框架軟件更小的體系結構,J2EE 中許多具體程序都是應用設計模式
來完成的,當你深入到J2EE 的內部代碼研究時,這點尤其明顯,因此,如果你不具備設計
模式的基礎知識(GoF 的設計模式),你很難快速的理解J2EE。不能理解J2EE,如何能靈活應
用?
3.J2EE 只是適合企業計算應用的框架軟件,但是GoF 的設計模式幾乎可以用於任何應用!
因此GoF 的設計模式應該是J2EE 的重要理論基礎之一。
所以說,GoF 的設計模式是Java 基礎知識和J2EE 框架知識之間一座隱性的"橋"。爲什麼說
隱性的?
GOF 的設計模式是一座隱性的"橋"
因爲很多人沒有注意到這點,學完Java 基礎語言就直接去學J2EE,有的甚至鴨子趕架,直
接使用起Weblogic 等具體J2EE 軟件,一段時間下來,發現不過如此,挺簡單好用,但是你
真正理解J2EE 了嗎?你在具體案例中的應用是否也是在延伸J2EE 的思想?
如果你不能很好的延伸J2EE 的思想,那你豈非是大炮轟蚊子,認識到J2EE 不是適合所有場
合的人至少是明智的,但我們更需要將J2EE 用對地方,那麼只有理解J2EE 此類框架軟件的
精髓,那麼你才能真正靈活應用Java 解決你的問題,甚至構架出你自己企業的框架來。(我
們不能總是使用別人設定好的框架,爲什麼不能有我們自己的框架?)
因此,首先你必須掌握GoF 的設計模式。雖然它是隱性,但不是可以越過的。
在著名的EJB 領域頂尖的專家Richard Monson-Haefel 的個人網站:www.EJBNow.com 中
Richard 極力推薦的幾本書中就有GoF 的《設計模式》,原文如下:
Design Patterns
Most developers claim to experience an epiphany reading this book. If you've never
read the Design Patterns book then you have suffered a very serious gap in your
programming education that should be remedied immediately.
翻譯: 很多程序員在讀完這本書,宣佈自己相當於經歷了一次"主顯節"(紀念那穌降生和受
洗的雙重節日),如果你從來沒有讀過這本書,你會在你的程序教育生涯裏存在一個嚴重裂溝,
所以你應該立即挽救彌補!
設計模式(Patterns in Java) -- http://www.jdon.com
6
設計模式(Patterns in Java) -- http://www.jdon.com
7
建築和軟件中模式之異同
CSDN 的透明特別推崇《建築的永恆之道》,認爲從中探尋到軟件的永恆之道,並就"設計模式
"寫了專門文章《探尋軟件的永恆之道》,其中很多觀點我看了很受啓發,以前我也將"設計
模式" 看成一個簡單的解決方案,沒有從一種高度來看待"設計模式"在軟件中地位,下面是
我自己的一些想法:
建築和軟件某些地方是可以來比喻的
特別是中國傳統建築,那是很講模式的,這些都是傳統文化使然,比如京劇一招一式都有套
路;中國畫,也有套路,樹應該怎麼畫法?有幾種畫法?藝術大家通常是創造出自己的套路,比
如明末清初,水墨畫法開始成熟,這時畫樹就不用勾勒這個模式了,而是一筆下去,濃淡幾個
葉子,待毛筆的水墨要乾枯時,畫一下樹幹,這樣,一個活生寫意的樹就畫出來.
我上面這些描述其實都是一種模式,創建模式的人是大師,但是拘泥於模式的人永遠是工匠.
再回到傳統建築中,中國的傳統建築是過分注重模式了,所以建築風格發展不大,基本分南北
兩派,大家有個感覺,旅遊時,到南方,你發現古代名居建築都差不多;北方由於受滿人等少數
民族的影響,在建築色彩上有些與南方迥異,但是很多細節地方都差不多.這些都是模式的體
現.
由於建築受材料和功用以及費用的影響,所用模式種類不多,這點是和軟件很大的不同.
正因爲這點不同,導致建築的管理模式和軟件的管理模式就有很多不同, 有些人認識不到這
點,就產生了可以大量使用"軟件藍領"的想法,因爲他羨慕建築中"民工"的低成本.
要知道軟件還有一個與建築截然相反的責任和用途,那就是:現代社會中,計劃感不上變化,
競爭激烈,所有一切變幻莫測,要應付所有這些變化,首推信息技術中的軟件,只有軟件能夠
幫助人類去應付各種變化.而這點正好與建築想反,建築是不能幫助人類去應付變化的,(它
自己反而要求穩固,老老實實幫助人遮風避雨,總不能叫人類在露天或樹葉下打開電腦編軟
件吧).
軟件要幫助人類去應付變化,這是軟件的首要責任,所以,軟件中模式產生的目的就和建築不
一樣了,建築中的模式產生可以因爲很多原因:建築大師的創意;材料的革新等;建築中這些
模式一旦產生,容易發生另外一個缺點,就是有時會阻礙建築本身的發展,因爲很多人會不思
創造,反覆使用老的模式進行設計,阻礙建築的發展.
但是在軟件中,這點正好相反,軟件模式的產生是因爲變化的東西太多,爲減輕人類的負擔,
將一些不變的東西先用模式固化,這樣讓人類可以更加集中精力對付變化的東西,所以在軟
件中大量反覆使用模式(我個人認爲這樣的軟件就叫框架軟件了,比如J2EE),不但沒阻礙軟
件的發展,反而是推動了軟件的發展.因爲其他使用這套軟件的人就可以將更多精力集中在
對付那些無法用模式的應用上來.
設計模式(Patterns in Java) -- http://www.jdon.com
8
可以關於建築和軟件中的模式作用可以總結如下:
在軟件中,模式是幫助人類向"變化"戰鬥,但是在軟件中還需要和'變化'直接面對面戰鬥的
武器:人的思維,特別是創造分析思維等等,這些是軟件真正的靈魂,這種思維可以說只要
有實踐需求(如有新項目)就要求發生,發生頻度高,人類的創造或分析思維決定了軟件的質
量和特點。
而在建築中,模式可以構成建築全部知識,當有新的需求(如有新項目),一般使用舊的模式
都可以完成,因此對人類的創造以及分析思維不是每個項目都必須的,也不是非常重要的,
對創造性的思維的需求只是屬於錦上添花(除非人類以後離開地球居住了)。
設計模式(Patterns in Java) -- http://www.jdon.com
9
設計模式之Factory
定義:提供創建對象的接口.
爲何使用?
工廠模式是我們最常用的模式了,著名的Jive 論壇系統,就大量使用了工廠模式.
爲什麼說工廠模式是最常用,因爲工廠模式就相當於創建對象的new. 工廠模式就是用來創
建對象的.
比如我們有一個類Sample 我們要創建Sample 的對象:
Sample sample=new Sample();
如果我們要在創建sample 之前做點事情,比如,賦值等,可以使用Sample 的構造函數:
Sample sample=new Sample(參數);
如果創建sample 時做的事情不是如賦值這樣簡單的事,可能是很長一段代碼,如果也寫入構
造函數中,那明顯的就違背了面向對象的原則.封裝(Encapsulation)和分派(Delegation);
我們需要將創建實例的責任與使用實例的責任分開, 使得語句
Sample sample=new Sample(參數);
就是簡單的責任:使用Sample 這個實例;至於創建Sample 的任務就交給了Factory 工廠模
式.
還有,如果Sample 有個繼承如MySample, 按照面向接口編程,我們需要將Sample 抽象成一
個接口.
現在Sample 是接口,有兩個子類MySample 和HisSample .我們要實例化他們時,如下:
Sample mysample=new MySample();
Sample hissample=new HisSample();
隨着項目的深入,Sample 可能還會"生出很多兒子出來", 那麼我們要對這些兒子一個個實
例化,更糟糕的是,可能還要對以前的代碼進行修改:加入後來生出兒子的實例.這在傳統程
序中是無法避免的.
但如果你一開始就有意識使用了工廠模式,這些麻煩就沒有了.
你會建立一個專門生產Sample 實例的工廠:
設計模式(Patterns in Java) -- http://www.jdon.com
10
public class Factory{
public static Sample creator(){
....
if (which==1)
return new MySample();
else if (which==2)
return new HisSample();
}
}
那麼在你的程序中,如果要實例化MySample 時.就使用
Sample sample=Factory.creator();
這樣,在整個就不涉及到Sample 的具體子類,達到封裝效果,也就減少錯誤修改的機會,這個
原理可以用很通俗的話來比喻:就是具體事情做得越多,越容易範錯誤.這每個做過具體工作
的人都深有體會,相反,官做得越高,說出的話越抽象越籠統,範錯誤可能性就越少.好象我們
從編程序中也能悟出人生道理?呵呵.
好了,言歸正傳,既然不可避免使用factory,那我們就認識一下工廠模式.
如何使用?
工廠模式中有: 工廠方法(Factory Method) 抽象工廠(Abstract Factory).
上例中,我們使用的是簡單的工廠方法. 這兩個模式沒有很明顯的區別,區別在於需要創建
對象的複雜程度上。如果我們創建對象的方法變得複雜了,我們就可能要將上例中Factory
變成抽象類,將共同部分封裝在抽象類中,不同部分使用子類實現:
public abstract class Factory{
public abstract Sample creator();
public abstract Sample2 creator();
}
public class SimpleFactory extends Factory{
public Sample creator(){
設計模式(Patterns in Java) -- http://www.jdon.com
11
......
}
public Sample2 creator(){
......
}
}
public class BombFactory extends Factory{
public Sample creator(){
......
}
public Sample2 creator(){
......
}
}
上例中我們只有一類產品接口Sample , 工廠方法和抽象工廠可以創建多個產品接口的實
例,比如Sample2 Sample3
FactoryMethod 往往只是創建單個的實例。Abstract Factory 創建一系列的實例組,這些實
例彼此相關。
舉例1
下圖是來自天極網" 閻宏"的爪哇語言抽象工廠創立性模式介紹中的抽象工廠圖:
設計模式(Patterns in Java) -- http://www.jdon.com
12
在這張圖中, 有兩類產品接口interface RAM 和interface CPU; 同時有兩個創建方
法:MacProducer 和PCProducer,這兩個創建方法中都有createCPU()和createRAM(),返回的
實例對象組是CPU 和RAM, 這是分別來自兩類產品接口,表面彼此是相關的.因此它是抽象工
廠.
舉例2
我們以Jive 的ForumFactory 爲例:
public abstract class ForumFactory {
private static Object initLock = new Object();
private static String className =
"com.jivesoftware.forum.database.DbForumFactory";
private static ForumFactory factory = null;
public static ForumFactory getInstance(Authorization authorization) {
//If no valid authorization passed in, return null.
if (authorization == null) {
return null;
}
//以下使用了Singleton 單態模式
if (factory == null) {
synchronized(initLock) {
if (factory == null) {
設計模式(Patterns in Java) -- http://www.jdon.com
13
......
try {
//動態轉載類
Class c = Class.forName(className);
factory = (ForumFactory)c.newInstance();
}
catch (Exception e) {
return null;
}
}
}
}
//Now, 返回proxy.用來限制授權對forum 的訪問
return new ForumFactoryProxy(authorization, factory,
factory.getPermissions(authorization));
}
//真正創建forum 的方法由繼承forumfactory 的子類去完成.
public abstract Forum createForum(String name, String description)
throws UnauthorizedException, ForumAlreadyExistsException;
....
}
因爲現在的Jive 是通過數據庫系統存放論壇帖子等內容數據,如果有人要擴展爲純粹的文
件系統存放的論壇帖子,這個工廠方法ForumFactory 就提供了提供動態接口:
private static String className =
"com.jivesoftware.forum.database.DbForumFactory";
你可以使用自己開發的創建forum 的方法代替
com.jivesoftware.forum.database.DbForumFactory 就可以.
在上面的一段代碼中一共用了三種模式,除了工廠模式外,還有Singleton 單態模式,以及
proxy 模式,proxy 模式主要用來授權用戶對forum 的訪問,因爲訪問forum有兩種人:一個是
註冊用戶一個是遊客guest,那麼那麼相應的權限就不一樣,而且這個權限是貫穿整個系統
的,因此建立一個proxy,類似網關的概念,可以很好的達到這個效果.
設計模式(Patterns in Java) -- http://www.jdon.com
14
好了.經過上述簡單描述,你對工廠模式應該有個簡單的印象,如果你要鑽研工廠模式,網絡
上有很多英文資料,不過我覺得過分鑽研也沒有必要,主要是使用實踐,實際使用中工廠模式
的變種很多,只要你知道這樣一個大概思路,相信在實踐中你會是工廠模式的設計創建高手!
設計模式(Patterns in Java) -- http://www.jdon.com
15
設計模式之Prototype(原型)
定義:
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象.
Prototype 模式允許一個對象再創建另外一個可定製的對象,根本無需知道任何如何創建的
細節,工作原理是:通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象
通過請求原型對象拷貝它們自己來實施創建。
如何使用?
因爲Java 中的提供clone()方法來實現對象的克隆(具體瞭解clone()按這裏),所以
Prototype 模式實現一下子變得很簡單.
以勺子爲例:
public abstract class AbstractSpoon implements Cloneable
{
String spoonName;
public void setSpoonName(String spoonName) {this.spoonName = spoonName;}
public String getSpoonName() {return this.spoonName;}
public Object clone()
{
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("AbstractSpoon is not Cloneable");
}
return object;
}
}
有兩個具體實現(ConcretePrototype):
public class SoupSpoon extends AbstractSpoon
{
public SoupSpoon()
{
setSpoonName("Soup Spoon");
設計模式(Patterns in Java) -- http://www.jdon.com
16
}
}
public class SaladSpoon extends AbstractSpoon
{
public SaladSpoon()
{
setSpoonName("Salad Spoon");
}
}
調用Prototype 模式很簡單:
AbstractSpoon spoon = new SoupSpoon();
AbstractSpoon spoon = new SaladSpoon();
當然也可以結合工廠模式來創建AbstractSpoon 實例。
在Java 中Prototype 模式變成clone()方法的使用,由於Java 的純潔的面向對象特性,使
得在Java 中使用設計模式變得很自然,兩者已經幾乎是渾然一體了。這反映在很多模式上,
如Interator 遍歷模式。
設計模式(Patterns in Java) -- http://www.jdon.com
17
設計模式之Builder
Builder 模式定義:
將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示.
Builder 模式是一步一步創建一個複雜的對象,它允許用戶可以只通過指定複雜對象的類型
和內容就可以構建它們.用戶不知道內部的具體構建細節.Builder 模式是非常類似抽象工
廠模式,細微的區別大概只有在反覆使用中才能體會到.
爲何使用?
是爲了將構建複雜對象的過程和它的部件解耦.注意: 是解耦過程和部件.
因爲一個複雜的對象,不但有很多大量組成部分,如汽車,有很多部件:車輪方向盤發動機
還有各種小零件等等,部件很多,但遠不止這些,如何將這些部件裝配成一輛汽車,這個裝配
過程也很複雜(需要很好的組裝技術),Builder 模式就是爲了將部件和組裝過程分開.
如何使用?
首先假設一個複雜對象是由多個部件組成的,Builder 模式是把複雜對象的創建和部件的創
建分別開來,分別用Builder 類和Director 類來表示.
首先,需要一個接口,它定義如何創建複雜對象的各個部件:
public interface Builder {
//創建部件A 比如創建汽車車輪
void buildPartA();
//創建部件B 比如創建汽車方向盤
void buildPartB();
//創建部件C 比如創建汽車發動機
void buildPartC();
//返回最後組裝成品結果(返回最後裝配好的汽車)
//成品的組裝過程不在這裏進行,而是轉移到下面的Director 類中
進行.
//從而實現瞭解耦過程和部件
Product getResult();
}
用Director 構建最後的複雜對象,而在上面Builder 接口中封裝的是如何創建一個個部件
(複雜對象是由這些部件組成的),也就是說Director 的內容是如何將部件最後組裝成成品:
設計模式(Patterns in Java) -- http://www.jdon.com
18
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
// 將部件partA partB partC 最後組成複雜對象
//這裏是將車輪方向盤和發動機組裝成汽車的過程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
Builder 的具體實現ConcreteBuilder:
通過具體完成接口Builder 來構建或裝配產品的部件;
定義並明確它所要創建的是什麼具體東西;
提供一個可以重新獲取產品的接口:
public class ConcreteBuilder implements Builder {
Part partA, partB, partC;
public void buildPartA() {
//這裏是具體如何構建partA 的代碼
};
public void buildPartB() {
//這裏是具體如何構建partB 的代碼
};
public void buildPartC() {
//這裏是具體如何構建partB 的代碼
};
public Product getResult() {
//返回最後組裝成品結果
};
}
複雜對象:產品Product:
設計模式(Patterns in Java) -- http://www.jdon.com
19
public interface Product { }
複雜對象的部件:
public interface Part { }
我們看看如何調用Builder 模式:
ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();
Builder 模式的應用
在Java 實際使用中,我們經常用到"池"(Pool)的概念,當資源提供者無法提供足夠的資源,
並且這些資源需要被很多用戶反覆共享時,就需要使用池.
"池"實際是一段內存,當池中有一些複雜的資源的"斷肢"(比如數據庫的連接池,也許有時一
個連接會中斷),如果循環再利用這些"斷肢",將提高內存使用效率,提高池的性能.修改
Builder 模式中Director 類使之能診斷"斷肢"斷在哪個部件上,再修復這個部件.
具體英文文章見:Recycle broken objects in resource pools
設計模式(Patterns in Java) -- http://www.jdon.com
20
設計模式之Singleton(單態)
定義:
Singleton 模式主要作用是保證在Java 應用程序中,一個Class 只有一個實例存在。
一個實例表示是單線程,在很多操作中,比如建立目錄數據庫連接都需要單線程操作,
Singleton 模式經常用於控制對系統資源的控制,我們常常看到工廠模式中工廠方法也用
Singleton 模式實現的.
如何使用?
一般Singleton 模式通常有幾種形式:
public class Singleton {
private static Singleton _instance = new Singleton();
public static Singleton getInstance() {
return _instance;
}
}
調用方法:
Singleton.getInstance()
第二種形式:
public class Singleton {
private static Singleton _instance = null;
public static Singleton getInstance() {
if (_instance==null)
_instancenew Singleton()
return _instance;
}
}
調用方法:
Singleton.getInstance()
設計模式(Patterns in Java) -- http://www.jdon.com
21
Singleton 模式在創建對象時使用較多,使用它也比較簡單。
設計模式(Patterns in Java) -- http://www.jdon.com
22
設計模式之Facade(外觀)
Facade 的定義: 爲子系統中的一組接口提供一個一致的界面.
Facade 一個典型應用就是數據庫JDBC 的應用,如下例對數據庫的操作:
public class DBCompare {
Connection conn = null;
PreparedStatement prep = null;
ResultSet rset = null;
try {
Class.forName( "<driver>" ).newInstance();
conn = DriverManager.getConnection( "<database>" );
String sql = "SELECT * FROM <table> WHERE <column name> = ?";
prep = conn.prepareStatement( sql );
prep.setString( 1, "<column value>" );
rset = prep.executeQuery();
if( rset.next() ) {
System.out.println( rset.getString( "<column name" ) );
}
} catch( SException e ) {
e.printStackTrace();
} finally {
rset.close();
prep.close();
conn.close();
}
}
上例是Jsp 中最通常的對數據庫操作辦法.
在應用中,經常需要對數據庫操作,每次都寫上述一段代碼肯定比較麻煩,需要將其中不變的
部分提煉出來,做成一個接口,這就引入了facade 外觀對象.如果以後我們更換
Class.forName 中的<driver>也非常方便,比如從Mysql 數據庫換到Oracle 數據庫,只要更
換facade 接口中的driver 就可以.
我們做成了一個Facade 接口,使用該接口,上例中的程序就可以更改如下:
public class DBCompare {
設計模式(Patterns in Java) -- http://www.jdon.com
23
String sql = "SELECT * FROM <table> WHERE <column name> = ?";
try {
Mysql msql=new mysql(sql);
prep.setString( 1, "<column value>" );
rset = prep.executeQuery();
if( rset.next() ) {
System.out.println( rset.getString( "<column name" ) );
}
} catch( SException e ) {
e.printStackTrace();
} finally {
mysql.close();
mysql=null;
}
}
可見非常簡單,所有程序對數據庫訪問都是使用改接口,降低系統的複雜性,增加了靈活性.
如果我們要使用連接池,也只要針對facade 接口修改就可以.
由上圖可以看出, facade 實際上是個理順系統間關係,降低系統間耦合度的一個常用的辦法,
也許你已經不知不覺在使用,儘管不知道它就是facade.
設計模式(Patterns in Java) -- http://www.jdon.com
24
設計模式之Proxy(代理)
理解並使用設計模式,能夠培養我們良好的面向對象編程習慣,同時在實際應用中,可以如魚
得水,享受遊刃有餘的樂趣.
Proxy 是比較有用途的一種模式,而且變種較多,應用場合覆蓋從小結構到整個系統的大結
構,Proxy 是代理的意思,我們也許有代理服務器等概念,代理概念可以解釋爲:在出發點到
目的地之間有一道中間層,意爲代理.
設計模式中定義: 爲其他對象提供一種代理以控制對這個對象的訪問.
爲什麼要使用Proxy?
1.授權機制不同級別的用戶對同一對象擁有不同的訪問權利,如Jive 論壇系統中,就使用
Proxy 進行授權機制控制,訪問論壇有兩種人:註冊用戶和遊客(未註冊用戶),Jive 中就通過
類似ForumProxy 這樣的代理來控制這兩種用戶對論壇的訪問權限.
2.某個客戶端不能直接操作到某個對象,但又必須和那個對象有所互動.
舉例兩個具體情況:
(1)如果那個對象是一個是很大的圖片,需要花費很長時間才能顯示出來,那麼當這個圖片包
含在文檔中時,使用編輯器或瀏覽器打開這個文檔,打開文檔必須很迅速,不能等待大圖片處
理完成,這時需要做個圖片Proxy 來代替真正的圖片.
(2)如果那個對象在Internet 的某個遠端服務器上,直接操作這個對象因爲網絡速度原因可
能比較慢,那我們可以先用Proxy 來代替那個對象.
總之原則是,對於開銷很大的對象,只有在使用它時才創建,這個原則可以爲我們節省很多寶
貴的Java 內存. 所以,有些人認爲Java 耗費資源內存,我以爲這和程序編制思路也有一定的
關係.
如何使用Proxy?
以Jive 論壇系統爲例,訪問論壇系統的用戶有多種類型:註冊普通用戶論壇管理者系統管
理者遊客,註冊普通用戶才能發言;論壇管理者可以管理他被授權的論壇;系統管理者可以
管理所有事務等,這些權限劃分和管理是使用Proxy 完成的.
Forum 是Jive 的核心接口,在Forum 中陳列了有關論壇操作的主要行爲,如論壇名稱論壇描
述的獲取和修改,帖子發表刪除編輯等.
在ForumPermissions 中定義了各種級別權限的用戶:
public class ForumPermissions implements Cacheable {
/**
設計模式(Patterns in Java) -- http://www.jdon.com
25
* Permission to read object.
*/
public static final int READ = 0;
/**
* Permission to administer the entire sytem.
*/
public static final int SYSTEM_ADMIN = 1;
/**
* Permission to administer a particular forum.
*/
public static final int FORUM_ADMIN = 2;
/**
* Permission to administer a particular user.
*/
public static final int USER_ADMIN = 3;
/**
* Permission to administer a particular group.
*/
public static final int GROUP_ADMIN = 4;
/**
* Permission to moderate threads.
*/
public static final int MODERATE_THREADS = 5;
/**
* Permission to create a new thread.
*/
public static final int CREATE_THREAD = 6;
/**
* Permission to create a new message.
*/
public static final int CREATE_MESSAGE = 7;
/**
* Permission to moderate messages.
*/
public static final int MODERATE_MESSAGES = 8;
設計模式(Patterns in Java) -- http://www.jdon.com
26
.....
public boolean isSystemOrForumAdmin() {
return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
}
.....
}
因此,Forum 中各種操作權限是和ForumPermissions 定義的用戶級別有關係的,作爲接口
Forum 的實現:ForumProxy 正是將這種對應關係聯繫起來.比如,修改Forum 的名稱,只有論壇
管理者或系統管理者可以修改,代碼如下:
public class ForumProxy implements Forum {
private ForumPermissions permissions;
private Forum forum;
this.authorization = authorization;
public ForumProxy(Forum forum, Authorization authorization,
ForumPermissions permissions)
{
this.forum = forum;
this.authorization = authorization;
this.permissions = permissions;
}
.....
public void setName(String name) throws UnauthorizedException,
ForumAlreadyExistsException
{
//只有是系統或論壇管理者纔可以修改名稱
if (permissions.isSystemOrForumAdmin()) {
forum.setName(name);
}
else {
throw new UnauthorizedException();
}
}
...
設計模式(Patterns in Java) -- http://www.jdon.com
27
}
而DbForum 纔是接口Forum 的真正實現,以修改論壇名稱爲例:
public class DbForum implements Forum, Cacheable {
...
public void setName(String name) throws
ForumAlreadyExistsException {
....
this.name = name;
//這裏真正將新名稱保存到數據庫中
saveToDb();
....
}
...
}
凡是涉及到對論壇名稱修改這一事件,其他程序都首先得和ForumProxy 打交道,由
ForumProxy 決定是否有權限做某一樣事情,ForumProxy 是個名副其實的"網關","安全代理
系統".
在平時應用中,無可避免總要涉及到系統的授權或安全體系,不管你有無意識的使用Proxy,
實際你已經在使用Proxy 了.
我們繼續結合Jive 談入深一點,下面要涉及到工廠模式了,如果你不瞭解工廠模式,請看我
的另外一篇文章:設計模式之Factory
我們已經知道,使用Forum 需要通過ForumProxy,Jive 中創建一個Forum 是使用Factory 模
式,有一個總的抽象類ForumFactory,在這個抽象類中,調用ForumFactory 是通過
getInstance()方法實現,這裏使用了Singleton(也是設計模式之一,由於介紹文章很多,我
就不寫了,看這裏),getInstance()返回的是ForumFactoryProxy.
爲什麼不返回ForumFactory,而返回ForumFactory 的實現ForumFactoryProxy?
原因是明顯的,需要通過代理確定是否有權限創建forum.
在ForumFactoryProxy 中我們看到代碼如下:
設計模式(Patterns in Java) -- http://www.jdon.com
28
public class ForumFactoryProxy extends ForumFactory {
protected ForumFactory factory;
protected Authorization authorization;
protected ForumPermissions permissions;
public ForumFactoryProxy(Authorization authorization, ForumFactory factory,
ForumPermissions permissions)
{
this.factory = factory;
this.authorization = authorization;
this.permissions = permissions;
}
public Forum createForum(String name, String description)
throws UnauthorizedException, ForumAlreadyExistsException
{
//只有系統管理者纔可以創建forum
if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
Forum newForum = factory.createForum(name, description);
return new ForumProxy(newForum, authorization, permissions);
}
else {
throw new UnauthorizedException();
}
}
方法createForum 返回的也是ForumProxy, Proxy 就象一道牆,其他程序只能和Proxy 交互
操作.
注意到這裏有兩個Proxy:ForumProxy 和ForumFactoryProxy. 代表兩個不同的職責:使用
Forum 和創建Forum;
至於爲什麼將使用對象和創建對象分開,這也是爲什麼使用Factory 模式的原因所在:是爲
了"封裝" "分派";換句話說,儘可能功能單一化,方便維護修改.
Jive 論壇系統中其他如帖子的創建和使用,都是按照Forum 這個思路而來的.
以上我們討論瞭如何使用Proxy 進行授權機制的訪問,Proxy 還可以對用戶隱藏另外一種稱
爲copy-on-write 的優化方式.拷貝一個龐大而複雜的對象是一個開銷很大的操作,如果拷
貝過程中,沒有對原來的對象有所修改,那麼這樣的拷貝開銷就沒有必要.用代理延遲這一拷
貝過程.
設計模式(Patterns in Java) -- http://www.jdon.com
29
比如:我們有一個很大的Collection,具體如hashtable,有很多客戶端會併發同時訪問它.
其中一個特別的客戶端要進行連續的數據獲取,此時要求其他客戶端不能再向hashtable 中
增加或刪除東東.
最直接的解決方案是:使用collection 的lock,讓這特別的客戶端獲得這個lock,進行連續
的數據獲取,然後再釋放lock.
public void foFetches(Hashtable ht){
synchronized(ht){
//具體的連續數據獲取動作..
}
}
但是這一辦法可能鎖住Collection 會很長時間,這段時間,其他客戶端就不能訪問該
Collection 了.
第二個解決方案是clone 這個Collection,然後讓連續的數據獲取針對clone 出來的那個
Collection 操作.這個方案前提是,這個Collection 是可clone 的,而且必須有提供深度
clone 的方法.Hashtable 就提供了對自己的clone 方法,但不是Key 和value 對象的clone,
關於Clone 含義可以參考專門文章.
public void foFetches(Hashtable ht){
Hashttable newht=(Hashtable)ht.clone();
}
問題又來了,由於是針對clone 出來的對象操作,如果原來的母體被其他客戶端操作修改了,
那麼對clone 出來的對象操作就沒有意義了.
最後解決方案:我們可以等其他客戶端修改完成後再進行clone,也就是說,這個特別的客戶
端先通過調用一個叫clone 的方法來進行一系列數據獲取操作.但實際上沒有真正的進行對
象拷貝,直至有其他客戶端修改了這個對象Collection.
使用Proxy 實現這個方案.這就是copy-on-write 操作.
Proxy 應用範圍很廣,現在流行的分佈計算方式RMI 和Corba 等都是Proxy 模式的應用.
更多Proxy 應用,見http://www.research.umbc.edu/~tarr/cs491/lectures/Proxy.pdf
Sun 公司的Explore the Dynamic Proxy API Dynamic Proxy Classes
設計模式(Patterns in Java) -- http://www.jdon.com
30
設計模式之Adapter(適配器)
定義:
將兩個不兼容的類糾合在一起使用,屬於結構型模式,需要有Adaptee(被適配者)和
Adaptor(適配器)兩個身份.
爲何使用?
我們經常碰到要將兩個沒有關係的類組合在一起使用,第一解決方案是:修改各自類的接口,
但是如果我們沒有源代碼,或者,我們不願意爲了一個應用而修改各自的接口。怎麼辦?
使用Adapter,在這兩種接口之間創建一個混合接口(混血兒).
如何使用?
實現Adapter 方式,其實"think in Java"的"類再生"一節中已經提到,有兩種方式:組合
(composition)和繼承(inheritance).
假設我們要打樁,有兩種類:方形樁圓形樁.
public class SquarePeg{
public void insert(String str){
System.out.println("SquarePeg insert():"+str);
}
}
public class RoundPeg{
public void insertIntohole(String msg){
System.out.println("RoundPeg insertIntoHole():"+msg);
} }
現在有一個應用,需要既打方形樁,又打圓形樁.那麼我們需要將這兩個沒有關係的類綜合應
用.假設RoundPeg 我們沒有源代碼,或源代碼我們不想修改,那麼我們使用Adapter 來實現這
個應用:
public class PegAdapter extends SquarePeg{
private RoundPeg roundPeg;
public PegAdapter(RoundPeg peg)(this.roundPeg=peg;)
public void insert(String str){ roundPeg.insertIntoHole(str);}
設計模式(Patterns in Java) -- http://www.jdon.com
31
}
在上面代碼中,RoundPeg 屬於Adaptee,是被適配者.PegAdapter 是Adapter,將Adaptee(被
適配者RoundPeg)和Target(目標SquarePeg)進行適配.實際上這是將組合方法
(composition)和繼承(inheritance)方法綜合運用.
PegAdapter 首先繼承SquarePeg,然後使用new 的組合生成對象方式,生成RoundPeg 的對
象roundPeg,再重載父類insert()方法。從這裏,你也瞭解使用new 生成對象和使用extends
繼承生成對象的不同,前者無需對原來的類修改,甚至無需要知道其內部結構和源代碼.
如果你有些Java 使用的經驗,已經發現,這種模式經常使用。
進一步使用
上面的PegAdapter 是繼承了SquarePeg,如果我們需要兩邊繼承,即繼承SquarePeg 又繼承
RoundPeg,因爲Java 中不允許多繼承,但是我們可以實現(implements)兩個接口
(interface)
public interface IRoundPeg{
public void insertIntoHole(String msg);
}
public interface ISquarePeg{
public void insert(String str);
}
下面是新的RoundPeg 和SquarePeg, 除了實現接口這一區別,和上面的沒什麼區別。
public class SquarePeg implements ISquarePeg{
public void insert(String str){
System.out.println("SquarePeg insert():"+str);
}
}
public class RoundPeg implements IRoundPeg{
public void insertIntohole(String msg){
System.out.println("RoundPeg insertIntoHole():"+msg);
}
}
下面是新的PegAdapter,叫做two-way adapter:
public class PegAdapter implements IRoundPeg,ISquarePeg{
private RoundPeg roundPeg;
設計模式(Patterns in Java) -- http://www.jdon.com
32
private SquarePeg squarePeg;
// 構造方法
public PegAdapter(RoundPeg peg){this.roundPeg=peg;}
// 構造方法
public PegAdapter(SquarePeg peg)(this.squarePeg=peg;)
public void insert(String str){ roundPeg.insertIntoHole(str);}
}
還有一種叫Pluggable Adapters,可以動態的獲取幾個adapters 中一個。使用Reflection
技術,可以動態的發現類中的Public 方法。
設計模式(Patterns in Java) -- http://www.jdon.com
33
設計模式之Composite(組合)
Composite 定義:
將對象以樹形結構組織起來,以達成“部分-整體” 的層次結構,使得客戶端對單個對象和
組合對象的使用具有一致性.
Composite 比較容易理解,想到Composite 就應該想到樹形結構圖。組合體內這些對象都有
共同接口,當組合體一個對象的方法被調用執行時,Composite 將遍歷(Iterator)整個樹形
結構,尋找同樣包含這個方法的對象並實現調用執行。可以用牽一動百來形容。
所以Composite 模式使用到Iterator 模式,和Chain of Responsibility 模式類似。
Composite 好處:
1.使客戶端調用簡單,客戶端可以一致的使用組合結構或其中單個對象,用戶就不必關係自
己處理的是單個對象還是整個組合結構,這就簡化了客戶端代碼。
2.更容易在組合體內加入對象部件. 客戶端不必因爲加入了新的對象部件而更改代碼。
如何使用Composite?
首先定義一個接口或抽象類,這是設計模式通用方式了,其他設計模式對接口內部定義限制
不多,Composite 卻有個規定,那就是要在接口內部定義一個用於訪問和管理Composite 組
合體的對象們(或稱部件Component).
下面的代碼是以抽象類定義,一般儘量用接口interface,
public abstract class Equipment
{
private String name;
//網絡價格
public abstract double netPrice();
//折扣價格
public abstract double discountPrice();
//增加部件方法
public boolean add(Equipment equipment) { return false; }
//刪除部件方法
public boolean remove(Equipment equipment) { return false; }
//注意這裏,這裏就提供一種用於訪問組合體類的部件方法。
public Iterator iter() { return null; }
public Equipment(final String name) { this.name=name; }
}
設計模式(Patterns in Java) -- http://www.jdon.com
34
抽象類Equipment 就是Component 定義,代表着組合體類的對象們,Equipment 中定義幾個
共同的方法。
public class Disk extends Equipment
{
public Disk(String name) { super(name); }
//定義Disk 網絡價格爲1
public double netPrice() { return 1.; }
//定義了disk 折扣價格是0.5 對摺。
public double discountPrice() { return .5; }
}
Disk 是組合體內的一個對象,或稱一個部件,這個部件是個單獨元素( Primitive)。
還有一種可能是,一個部件也是一個組合體,就是說這個部件下面還有'兒子',這是樹形結
構中通常的情況,應該比較容易理解。現在我們先要定義這個組合體:
abstract class CompositeEquipment extends Equipment
{
private int i=0;
//定義一個Vector 用來存放'兒子'
private Lsit equipment=new ArrayList();
public CompositeEquipment(String name) { super(name); }
public boolean add(Equipment equipment) {
this.equipment.add(equipment);
return true;
}
public double netPrice()
{
double netPrice=0.;
Iterator iter=equipment.iterator();
for(iter.hasNext())
netPrice+=((Equipment)iter.next()).netPrice();
return netPrice;
}
public double discountPrice()
{
double discountPrice=0.;
Iterator iter=equipment.iterator();
for(iter.hasNext())
設計模式(Patterns in Java) -- http://www.jdon.com
35
discountPrice+=((Equipment)iter.next()).discountPrice();
return discountPrice;
}
//注意這裏,這裏就提供用於訪問自己組合體內的部件方法。
//上面dIsk 之所以沒有,是因爲Disk 是個單獨(Primitive)的元素.
public Iterator iter()
{
return equipment.iterator() ;
{
//重載Iterator 方法
public boolean hasNext() { return i<equipment.size(); }
//重載Iterator 方法
public Object next()
{
if(hasNext())
return equipment.elementAt(i++);
else
throw new NoSuchElementException();
}
}
上面CompositeEquipment 繼承了Equipment,同時爲自己裏面的對象們提供了外部訪問的方
法,重載了Iterator,Iterator 是Java 的Collection 的一個接口,是Iterator 模式的實現.
我們再看看CompositeEquipment 的兩個具體類:盤盒Chassis 和箱子Cabinet,箱子裏面可
以放很多東西,如底板,電源盒,硬盤盒等;盤盒裏面可以放一些小設備,如硬盤軟驅等。
無疑這兩個都是屬於組合體性質的。
public class Chassis extends CompositeEquipment
{
public Chassis(String name) { super(name); }
public double netPrice() { return 1.+super.netPrice(); }
public double discountPrice()
{ return .5+super.discountPrice(); }
}
public class Cabinet extends CompositeEquipment
{
設計模式(Patterns in Java) -- http://www.jdon.com
36
public Cabinet(String name) { super(name); }
public double netPrice() { return 1.+super.netPrice(); }
public double discountPrice()
{ return .5+super.discountPrice(); }
}
至此我們完成了整個Composite 模式的架構。
我們可以看看客戶端調用Composote 代碼:
Cabinet cabinet=new Cabinet("Tower");
Chassis chassis=new Chassis("PC Chassis");
//將PC Chassis 裝到Tower 中(將盤盒裝到箱子裏)
cabinet.add(chassis);
//將一個10GB 的硬盤裝到PC Chassis (將硬盤裝到盤盒裏)
chassis.add(new Disk("10 GB"));
//調用netPrice()方法;
System.out.println("netPrice="+cabinet.netPrice());
System.out.println("discountPrice="+cabinet.discountPrice());
上面調用的方法netPrice()或discountPrice(),實際上Composite 使用Iterator 遍歷了
整個樹形結構,尋找同樣包含這個方法的對象並實現調用執行.
Composite 是個很巧妙體現智慧的模式,在實際應用中,如果碰到樹形結構,我們就可以嘗
試是否可以使用這個模式。
以論壇爲例,一個版(forum)中有很多帖子(message),這些帖子有原始貼,有對原始貼的回
應貼,是個典型的樹形結構,那麼當然可以使用Composite 模式,那麼我們進入Jive 中看
看,是如何實現的.
Jive 解剖
在Jive 中ForumThread 是ForumMessages 的容器container(組合體).也就是說,
ForumThread 類似我們上例中的CompositeEquipment.它和messages 的關係如圖:
[thread]
|- [message]
|- [message]
|- [message]
|- [message]
|- [message]
我們在ForumThread 看到如下代碼:
設計模式(Patterns in Java) -- http://www.jdon.com
37
public interface ForumThread {
....
public void addMessage(ForumMessage parentMessage,
ForumMessage newMessage)
throws UnauthorizedException;
public void deleteMessage(ForumMessage message)
throws UnauthorizedException;
public Iterator messages();
....
}
類似CompositeEquipment, 提供用於訪問自己組合體內的部件方法: 增加刪除遍歷.
結合我的其他模式中對Jive 的分析,我們已經基本大體理解了Jive 論壇體系的框架,如果
你之前不理解設計模式,而直接去看Jive 源代碼,你肯定無法看懂。
設計模式(Patterns in Java) -- http://www.jdon.com
38
設計模式之Decorator(油漆工)
Decorator 常被翻譯成"裝飾",我覺得翻譯成"油漆工"更形象點,油漆工(decorator)是用來
刷油漆的,那麼被刷油漆的對象我們稱decoratee.這兩種實體在Decorator 模式中是必須
的.
Decorator 定義:
動態給一個對象添加一些額外的職責,就象在牆上刷油漆.使用Decorator 模式相比用生成
子類方式達到功能的擴充顯得更爲靈活.
爲什麼使用Decorator?
我們通常可以使用繼承來實現功能的拓展,如果這些需要拓展的功能的種類很繁多,那麼勢
必生成很多子類,增加系統的複雜性,同時,使用繼承實現功能拓展,我們必須可預見這些拓
展功能,這些功能是編譯時就確定了,是靜態的.
使用Decorator 的理由是:這些功能需要由用戶動態決定加入的方式和時機.Decorator 提供
了"即插即用"的方法,在運行期間決定何時增加何種功能.
如何使用?
舉Adapter 中的打樁示例,在Adapter 中有兩種類:方形樁圓形樁,Adapter 模式展示如何綜
合使用這兩個類,在Decorator 模式中,我們是要在打樁時增加一些額外功能,比如,挖坑在
樁上釘木板等,不關心如何使用兩個不相關的類.
我們先建立一個接口:
public interface Work
{
public void insert();
}
接口Work 有一個具體實現:插入方形樁或圓形樁,這兩個區別對Decorator 是無所謂.我們以
插入方形樁爲例:
public class SquarePeg implements Work{
public void insert(){
System.out.println("方形樁插入");
}
}
設計模式(Patterns in Java) -- http://www.jdon.com
39
現在有一個應用:需要在樁打入前,挖坑,在打入後,在樁上釘木板,這些額外的功能是動態,
可能隨意增加調整修改,比如,可能又需要在打樁之後釘架子(只是比喻).
那麼我們使用Decorator 模式,這裏方形樁SquarePeg 是decoratee(被刷油漆者),我們需要
在decoratee 上刷些"油漆",這些油漆就是那些額外的功能.
public class Decorator implements Work{
private Work work;
//額外增加的功能被打包在這個List 中
private ArrayList others = new ArrayList();
//在構造器中使用組合new 方式,引入Work 對象;
public Decorator(Work work)
{
this.work=work;
others.add("挖坑");
others.add("釘木板");
}
public void insert(){
newMethod();
}
//在新方法中,我們在insert 之前增加其他方法,這裏次序先後是用
戶靈活指定的
public void newMethod()
{
otherMethod();
work.insert();
}
public void otherMethod()
{
ListIterator listIterator = others.listIterator();
while (listIterator.hasNext())
{
設計模式(Patterns in Java) -- http://www.jdon.com
40
System.out.println(((String)(listIterator.next()))
+ " 正在進行");
}
}
}
在上例中,我們把挖坑和釘木板都排在了打樁insert 前面,這裏只是舉例說明額外功能次序
可以任意安排.
好了,Decorator 模式出來了,我們看如何調用:
Work squarePeg = new SquarePeg();
Work decorator = new Decorator(squarePeg);
decorator.insert();
Decorator 模式至此完成.
如果你細心,會發現,上面調用類似我們讀取文件時的調用:
FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);
實際上Java 的I/O API 就是使用Decorator 實現的,I/O 變種很多,如果都採取繼承方法,
將會產生很多子類,顯然相當繁瑣.
Jive 中的Decorator 實現
在論壇系統中,有些特別的字是不能出現在論壇中如"打倒XXX",我們需要過濾這些"反動"
的字體.不讓他們出現或者高亮度顯示.
在IBM Java 專欄中專門談Jive 的文章中,有談及Jive 中ForumMessageFilter.java 使用了
Decorator 模式,其實,該程序並沒有真正使用Decorator,而是提示說:針對特別論壇可以設
計額外增加的過濾功能,那麼就可以重組ForumMessageFilter 作爲Decorator 模式了.
所以,我們在分辨是否真正是Decorator 模式,以及會真正使用Decorator 模式,一定要把握
好Decorator 模式的定義,以及其中參與的角色(Decoratee 和Decorator).
設計模式(Patterns in Java) -- http://www.jdon.com
41
設計模式之Bridge
Bridge 定義:
將抽象和行爲劃分開來,各自獨立,但能動態的結合.
爲什麼使用?
通常,當一個抽象類或接口有多個具體實現(concrete subclass),這些concrete 之間關係可
能有以下兩種:
1. 這多個具體實現之間恰好是並列的,如前面舉例,打樁,有兩個concrete class:方形樁和
圓形樁;這兩個形狀上的樁是並列的,沒有概念上的重複,那麼我們只要使用繼承就可以了.
2.實際應用上,常常有可能在這多個concrete class 之間有概念上重疊.那麼需要我們把抽
象共同部分和行爲共同部分各自獨立開來,原來是準備放在一個接口裏,現在需要設計兩個
接口,分別放置抽象和行爲.
例如,一杯咖啡爲例,有中杯和大杯之分,同時還有加奶不加奶之分. 如果用單純的繼承,這
四個具體實現(中杯大杯加奶不加奶)之間有概念重疊,因爲有中杯加奶,也有中杯不加奶,
如果再在中杯這一層再實現兩個繼承,很顯然混亂,擴展性極差.那我們使用Bridge 模式來
實現它.
如何實現?
以上面提到的咖啡爲例. 我們原來打算只設計一個接口(抽象類),使用Bridge 模式後,我
們需要將抽象和行爲分開,加奶和不加奶屬於行爲,我們將它們抽象成一個專門的行爲接口.
先看看抽象部分的接口代碼:
public abstract class Coffee
{
CoffeeImp coffeeImp;
public void setCoffeeImp() {
this.CoffeeImp = CoffeeImpSingleton.getTheCoffeImp();
}
public CoffeeImp getCoffeeImp() {return this.CoffeeImp;}
public abstract void pourCoffee();
}
其中CoffeeImp 是加不加奶的行爲接口,看其代碼如下:
設計模式(Patterns in Java) -- http://www.jdon.com
42
public abstract class CoffeeImp
{
public abstract void pourCoffeeImp();
}
現在我們有了兩個抽象類,下面我們分別對其進行繼承,實現concrete class:
//中杯
public class MediumCoffee extends Coffee
{
public MediumCoffee() {setCoffeeImp();}
public void pourCoffee()
{
CoffeeImp coffeeImp = this.getCoffeeImp();
//我們以重複次數來說明是衝中杯還是大杯,重複2 次是中杯
for (int i = 0; i < 2; i++)
{
coffeeImp.pourCoffeeImp();
}
}
}
//大杯
public class SuperSizeCoffee extends Coffee
{
public SuperSizeCoffee() {setCoffeeImp();}
public void pourCoffee()
{
CoffeeImp coffeeImp = this.getCoffeeImp();
//我們以重複次數來說明是衝中杯還是大杯,重複5 次是大杯
for (int i = 0; i < 5; i++)
{
coffeeImp.pourCoffeeImp();
}
}
}
設計模式(Patterns in Java) -- http://www.jdon.com
43
上面分別是中杯和大杯的具體實現.下面再對行爲CoffeeImp 進行繼承:
//加奶
public class MilkCoffeeImp extends CoffeeImp
{
MilkCoffeeImp() {}
public void pourCoffeeImp()
{
System.out.println("加了美味的牛奶");
}
}
//不加奶
public class FragrantCoffeeImp extends CoffeeImp
{
FragrantCoffeeImp() {}
public void pourCoffeeImp()
{
System.out.println("什麼也沒加,清香");
}
}
Bridge 模式的基本框架我們已經搭好了,別忘記定義中還有一句:動態結合,我們現在可以
喝到至少四種咖啡:
1.中杯加奶
2.中杯不加奶
3.大杯加奶
4.大杯不加奶
看看是如何動態結合的,在使用之前,我們做個準備工作,設計一個單態類(Singleton)用來
hold 當前的CoffeeImp:
public class CoffeeImpSingleton
{
private static CoffeeImp coffeeImp;
public CoffeeImpSingleton(CoffeeImp coffeeImpIn)
{this.coffeeImp = coffeeImpIn;}
public static CoffeeImp getTheCoffeeImp()
{
設計模式(Patterns in Java) -- http://www.jdon.com
44
return coffeeImp;
}
}
看看中杯加奶和大杯加奶是怎麼出來的:
//拿出牛奶
CoffeeImpSingleton coffeeImpSingleton = new CoffeeImpSingleton(new
MilkCoffeeImp());
//中杯加奶
MediumCoffee mediumCoffee = new MediumCoffee();
mediumCoffee.pourCoffee();
//大杯加奶
SuperSizeCoffee superSizeCoffee = new SuperSizeCoffee();
superSizeCoffee.pourCoffee();
注意: Bridge 模式的執行類如CoffeeImp 和Coffee 是一對一的關係, 正確創建CoffeeImp
是該模式的關鍵,
Bridge 模式在EJB 中的應用
EJB 中有一個Data Access Object (DAO)模式,這是將商業邏輯和具體數據資源分開的,因爲
不同的數據庫有不同的數據庫操作.將操作不同數據庫的行爲獨立抽象成一個行爲接口DAO.
如下:
1.Business Object (類似Coffee)
實現一些抽象的商業操作:如尋找一個用戶下所有的訂單
涉及數據庫操作都使用DAOImplementor.
2.Data Access Object (類似CoffeeImp)
一些抽象的對數據庫資源操作
3.DAOImplementor 如OrderDAOCS, OrderDAOOracle, OrderDAOSybase(類似MilkCoffeeImp
FragrantCoffeeImp)
具體的數據庫操作,如"INSERT INTO "等語句,OrderDAOOracle 是Oracle OrderDAOSybase
是Sybase 數據庫.
4.數據庫(Cloudscape, Oracle, or Sybase database via JDBC API)
設計模式(Patterns in Java) -- http://www.jdon.com
45
設計模式之Flyweight(享元)
板橋里人http://www.jdon.com 2002/05/02
Flyweight 定義:
避免大量擁有相同內容的小類的開銷(如耗費內存),使大家共享一個類(元類).
爲什麼使用?
面嚮對象語言的原則就是一切都是對象,但是如果真正使用起來,有時對象數可能顯得很龐
大,比如,字處理軟件,如果以每個文字都作爲一個對象,幾千個字,對象數就是幾千,無疑耗
費內存,那麼我們還是要"求同存異",找出這些對象羣的共同點,設計一個元類,封裝可以被
共享的類,另外,還有一些特性是取決於應用(context),是不可共享的,這也Flyweight 中兩
個重要概念內部狀態intrinsic 和外部狀態extrinsic 之分.
說白點,就是先捏一個的原始模型,然後隨着不同場合和環境,再產生各具特徵的具體模型,
很顯然,在這裏需要產生不同的新對象,所以Flyweight 模式中常出現Factory 模
式.Flyweight 的內部狀態是用來共享的,Flyweight factory 負責維護一個Flyweight
pool(模式池)來存放內部狀態的對象.
Flyweight 模式是一個提高程序效率和性能的模式,會大大加快程序的運行速度.應用場合
很多:比如你要從一個數據庫中讀取一系列字符串,這些字符串中有許多是重複的,那麼我們
可以將這些字符串儲存在Flyweight 池(pool)中.
如何使用?
我們先從Flyweight 抽象接口開始:
public interface Flyweight
{
public void operation( ExtrinsicState state );
}
//用於本模式的抽象數據類型(自行設計)
public interface ExtrinsicState { }
下面是接口的具體實現(ConcreteFlyweight) ,併爲內部狀態增加內存空間,
ConcreteFlyweight 必須是可共享的,它保存的任何狀態都必須是內部(intrinsic),也就是
說,ConcreteFlyweight 必須和它的應用環境場合無關.
public class ConcreteFlyweight implements Flyweight {
private IntrinsicState state;
public void operation( ExtrinsicState state )
設計模式(Patterns in Java) -- http://www.jdon.com
46
{
//具體操作
}
}
當然,並不是所有的Flyweight 具體實現子類都需要被共享的,所以還有另外一種不共享的
ConcreteFlyweight:
public class UnsharedConcreteFlyweight implements Flyweight {
public void operation( ExtrinsicState state ) { }
}
Flyweight factory 負責維護一個Flyweight 池(存放內部狀態),當客戶端請求一個共享
Flyweight 時,這個factory 首先搜索池中是否已經有可適用的,如果有,factory 只是簡單返
回送出這個對象,否則,創建一個新的對象,加入到池中,再返回送出這個對象.池
public class FlyweightFactory {
//Flyweight pool
private Hashtable flyweights = new Hashtable();
public Flyweight getFlyweight( Object key ) {
Flyweight flyweight = (Flyweight) flyweights.get(key);
if( flyweight == null ) {
//產生新的ConcreteFlyweight
flyweight = new ConcreteFlyweight();
flyweights.put( key, flyweight );
}
return flyweight;
}
}
至此,Flyweight 模式的基本框架已經就緒,我們看看如何調用:
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly1 = factory.getFlyweight( "Fred" );
Flyweight fly2 = factory.getFlyweight( "Wilma" );
......
設計模式(Patterns in Java) -- http://www.jdon.com
47
從調用上看,好象是個純粹的Factory 使用,但奧妙就在於Factory 的內部設計上.
Flyweight 模式在XML 等數據源中應用
我們上面已經提到,當大量從數據源中讀取字符串,其中肯定有重複的,那麼我們使用
Flyweight 模式可以提高效率,以唱片CD 爲例,在一個XML 文件中,存放了多個CD 的資料.
每個CD 有三個字段:
1.出片日期(year)
2.歌唱者姓名等信息(artist)
3.唱片曲目(title)
其中,歌唱者姓名有可能重複,也就是說,可能有同一個演唱者的多個不同時期不同曲目的
CD.我們將"歌唱者姓名"作爲可共享的ConcreteFlyweight.其他兩個字段作爲
UnsharedConcreteFlyweight.
首先看看數據源XML 文件的內容:
<?xml version="1.0"?>
<collection>
<cd>
<title>Another Green World</title>
<year>1978</year>
<artist>Eno, Brian</artist>
</cd>
<cd>
<title>Greatest Hits</title>
<year>1950</year>
<artist>Holiday, Billie</artist>
</cd>
<cd>
<title>Taking Tiger Mountain (by strategy)</title>
<year>1977</year>
<artist>Eno, Brian</artist>
</cd>
.......
</collection>
設計模式(Patterns in Java) -- http://www.jdon.com
48
雖然上面舉例CD 只有3 張,CD 可看成是大量重複的小類,因爲其中成分只有三個字段,而且
有重複的(歌唱者姓名).
CD 就是類似上面接口Flyweight:
public class CD {
private String title;
private int year;
private Artist artist;
public String getTitle() { return title; }
public int getYear() { return year; }
public Artist getArtist() { return artist; }
public void setTitle(String t){ title = t;}
public void setYear(int y){year = y;}
public void setArtist(Artist a){artist = a;}
}
將"歌唱者姓名"作爲可共享的ConcreteFlyweight:
public class Artist {
//內部狀態
private String name;
// note that Artist is immutable.
String getName(){return name;}
Artist(String n){
name = n;
}
}
再看看Flyweight factory,專門用來製造上面的可共享的ConcreteFlyweight:Artist
public class ArtistFactory {
設計模式(Patterns in Java) -- http://www.jdon.com
49
Hashtable pool = new Hashtable();
Artist getArtist(String key){
Artist result;
result = (Artist)pool.get(key);
////產生新的Artist
if(result == null) {
result = new Artist(key);
pool.put(key,result);
}
return result;
}
}
當你有幾千張甚至更多CD 時,Flyweight 模式將節省更多空間,共享的flyweight 越多,空間
節省也就越大.
設計模式(Patterns in Java) -- http://www.jdon.com
50
設計模式之Template
Template 定義:
定義一個操作中算法的骨架,將一些步驟的執行延遲到其子類中.
其實Java 的抽象類本來就是Template 模式,因此使用很普遍.而且很容易理解和使用,我們
直接以示例開始:
public abstract class Benchmark
{
/**
* 下面操作是我們希望在子類中完成
*/
public abstract void benchmark();
/**
* 重複執行benchmark 次數
*/
public final long repeat (int count) {
if (count <= 0)
return 0;
else {
long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++)
benchmark();
long stopTime = System.currentTimeMillis();
return stopTime - startTime;
}
}
}
在上例中,我們希望重複執行benchmark()操作,但是對benchmark()的具體內容沒有說明,
而是延遲到其子類中描述:
public class MethodBenchmark extends Benchmark
{
/**
* 真正定義benchmark 內容
*/
設計模式(Patterns in Java) -- http://www.jdon.com
51
public void benchmark() {
for (int i = 0; i < Integer.MAX_VALUE; i++){
System.out.printtln("i="+i);
}
}
}
至此,Template 模式已經完成,是不是很簡單?看看如何使用:
Benchmark operation = new MethodBenchmark();
long duration = operation.repeat(Integer.parseInt(args[0].trim()));
System.out.println("The operation took " + duration + " milliseconds");
也許你以前還疑惑抽象類有什麼用,現在你應該徹底明白了吧? 至於這樣做的好處,很顯然
啊,擴展性強,以後Benchmark 內容變化,我只要再做一個繼承子類就可以,不必修改其他應
用代碼.
設計模式(Patterns in Java) -- http://www.jdon.com
52
設計模式之Memento(備忘機制)
Memento 定義:
memento 是一個保存另外一個對象內部狀態拷貝的對象.這樣以後就可以將該對象恢復到原
先保存的狀態.
Memento 模式相對也比較好理解,我們看下列代碼:
public class Originator {
private int number;
private File file = null;
public Originator(){}
// 創建一個Memento
public Memento getMemento(){
return new Memento(this);
}
// 恢復到原始值
public void setMemento(Memento m){
number = m.number;
file = m.file;
}
}
我們再看看Memento 類:
private class Memento implements java.io.Serializable{
private int number;
private File file = null;
public Memento( Originator o){
number = o.number;
設計模式(Patterns in Java) -- http://www.jdon.com
53
file = o.file;
}
}
可見Memento 中保存了Originator 中的number 和file 的值. 通過調用Originator 中
number 和file 值改變的話,通過調用setMemento()方法可以恢復.
Memento 模式的缺點是耗費大,如果內部狀態很多,再保存一份,無意要浪費大量內存.
Memento 模式在Jsp+Javabean 中的應用
在Jsp 應用中,我們通常有很多表單要求用戶輸入,比如用戶註冊,需要輸入姓名和Email 等,
如果一些表項用戶沒有填寫或者填寫錯誤,我們希望在用戶按"提交Submit"後,通過Jsp 程
序檢查,發現確實有未填寫項目,則在該項目下紅字顯示警告或錯誤,同時,還要顯示用戶剛
才已經輸入的表項.
如下圖中First Name 是用戶已經輸入,Last Name 沒有輸入,我們則提示紅字警告.:
設計模式(Patterns in Java) -- http://www.jdon.com
54
這種技術的實現,就是利用了Javabean 的scope="request"或scope="session"特性,也就是
Memento 模式.
具體示例和代碼見JavaWorld 的英文原文, Javabean 表單輸入特性參見我的另外一篇文
章.
設計模式(Patterns in Java) -- http://www.jdon.com
55
設計模式之Observer
Java 深入到一定程度,就不可避免的碰到設計模式(design pattern)這一概念,瞭解設計模
式,將使自己對java 中的接口或抽象類應用有更深的理解.設計模式在java 的中型系統中應
用廣泛,遵循一定的編程模式,才能使自己的代碼便於理解,易於交流,Observer(觀察者)模
式是比較常用的一個模式,尤其在界面設計中應用廣泛,而本站所關注的是Java 在電子商務
系統中應用,因此想從電子商務實例中分析Observer 的應用.
雖然網上商店形式多樣,每個站點有自己的特色,但也有其一般的共性,單就"商品的變化,以
便及時通知訂戶"這一點,是很多網上商店共有的模式,這一模式類似Observer patern.
具體的說,如果網上商店中商品在名稱價格等方面有變化,如果系統能自動通知會員,將是
網上商店區別傳統商店的一大特色.這就需要在商品product 中加入Observer 這樣角色,以
便product 細節發生變化時,Observer 能自動觀察到這種變化,並能進行及時的update 或
notify 動作.
Java 的API 還爲爲我們提供現成的Observer 接口Java.util.Observer.我們只要直接使用
它就可以.
我們必須extends Java.util.Observer 才能真正使用它:
1.提供Add/Delete observer 的方法;
2.提供通知(notisfy) 所有observer 的方法;
//產品類可供Jsp 直接使用UseBean 調用該類主要執行產品數據庫插
設計模式(Patterns in Java) -- http://www.jdon.com
56
入更新
public class product extends Observable{
private String name;
private float price;
public String getName(){ return name;}
public void setName(){
this.name=name;
//設置變化點
setChanged();
notifyObservers(name);
}
public float getPrice(){ return price;}
public void setPrice(){
this.price=price;
//設置變化點
setChanged();
notifyObservers(new Float(price));
}
//以下可以是數據庫更新插入命令.
public void saveToDb(){
.....................
}
我們注意到,在product 類中的setXXX 方法中,我們設置了notify(通知)方法, 當Jsp 表
單調用setXXX(如何調用見我的另外一篇文章),實際上就觸發了notisfyObservers 方法,這
將通知相應觀察者應該採取行動了.
下面看看這些觀察者的代碼,他們究竟採取了什麼行動:
//觀察者NameObserver 主要用來對產品名稱(name)進行觀察的
public class NameObserver implements Observer{
private String name=null;
public void update(Observable obj,Object arg){
設計模式(Patterns in Java) -- http://www.jdon.com
57
if (arg instanceof String){
name=(String)arg;
//產品名稱改變值在name 中
System.out.println("NameObserver :name changet to "+name);
}
}
}
//觀察者PriceObserver 主要用來對產品價格(price)進行觀察的
public class PriceObserver implements Observer{
private float price=0;
public void update(Observable obj,Object arg){
if (arg instanceof Float){
price=((Float)arg).floatValue();
System.out.println("PriceObserver :price changet to "+price);
}
}
}
Jsp 中我們可以來正式執行這段觀察者程序:
<jsp:useBean id="product" scope="session" class="Product" />
<jsp:setProperty name="product" property="*" />
<jsp:useBean id="nameobs" scope="session" class="NameObserver" />
<jsp:setProperty name="product" property="*" />
<jsp:useBean id="priceobs" scope="session" class="PriceObserver" />
<jsp:setProperty name="product" property="*" />
<%
設計模式(Patterns in Java) -- http://www.jdon.com
58
if (request.getParameter("save")!=null)
{
product.saveToDb();
out.println("產品數據變動保存! 並已經自動通知客戶");
}else{
//加入觀察者
product.addObserver(nameobs);
product.addObserver(priceobs);
%>
//request.getRequestURI()是產生本jsp 的程序名,就是自己調用自己
<form action="<%=request.getRequestURI()%>" method=post>
<input type=hidden name="save" value="1">
產品名稱:<input type=text name="name" >
產品價格:<input type=text name="price">
<input type=submit>
</form>
<%
}
%>
執行改Jsp 程序,會出現一個表單錄入界面, 需要輸入產品名稱產品價格, 點按Submit 後,
還是執行該jsp 的
if (request.getParameter("save")!=null)之間的代碼.
由於這裏使用了數據javabeans 的自動賦值概念,實際程序自動執行了setName setPrice
語句.你會在服務器控制檯中發現下面信息::
設計模式(Patterns in Java) -- http://www.jdon.com
59
NameObserver :name changet to ?????(Jsp 表單中輸入的產品名稱)
PriceObserver :price changet to ???(Jsp 表單中輸入的產品價格);
這說明觀察者已經在行動了.!!
同時你會在執行jsp 的瀏覽器端得到信息:
產品數據變動保存! 並已經自動通知客戶
上文由於使用jsp 概念,隱含很多自動動作,現將調用觀察者的Java 代碼寫如下:
public class Test {
public static void main(String args[]){
Product product=new Product();
NameObserver nameobs=new NameObserver();
PriceObserver priceobs=new PriceObserver();
//加入觀察者
product.addObserver(nameobs);
product.addObserver(priceobs);
product.setName("橘子紅了");
product.setPrice(9.22f);
}
}
你會在發現下面信息::
NameObserver :name changet to 橘子紅了
PriceObserver :price changet to 9.22
設計模式(Patterns in Java) -- http://www.jdon.com
60
這說明觀察者在行動了.!!
設計模式(Patterns in Java) -- http://www.jdon.com
61
設計模式之Chain of Responsibility(職責鏈)
Chain of Responsibility 定義
Chain of Responsibility(CoR) 是用一系列類(classes)試圖處理一個請求request,這些
類之間是一個鬆散的耦合,唯一共同點是在他們之間傳遞request. 也就是說,來了一個請
求,A 類先處理,如果沒有處理,就傳遞到B 類處理,如果沒有處理,就傳遞到C 類處理,
就這樣象一個鏈條(chain)一樣傳遞下去。
如何使用?
雖然這一段是如何使用CoR,但是也是演示什麼是CoR.
有一個Handler 接口:
public interface Handler{
public void handleRequest();
}
這是一個處理request 的事例, 如果有多種request,比如請求幫助請求打印或請求格
式化:
最先想到的解決方案是:在接口中增加多個請求:
public interface Handler{
public void handleHelp();
public void handlePrint();
public void handleFormat();
}
具體是一段實現接口Handler 代碼:
public class ConcreteHandler implements Handler{
private Handler successor;
public ConcreteHandler(Handler successor){
this.successor=successor;
}
public void handleHelp(){
//具體處理請求Help 的代碼
...
}
設計模式(Patterns in Java) -- http://www.jdon.com
62
public void handlePrint(){
//如果是print 轉去處理Print
successor.handlePrint();
}
public void handleFormat(){
//如果是Format 轉去處理format
successor.handleFormat();
}
}
一共有三個這樣的具體實現類,上面是處理help,還有處理Print 處理Format 這大概是我
們最常用的編程思路。
雖然思路簡單明瞭,但是有一個擴展問題,如果我們需要再增加一個請求request 種類,需
要修改接口及其每一個實現。
第二方案:將每種request 都變成一個接口,因此我們有以下代碼:
public interface HelpHandler{
public void handleHelp();
}
public interface PrintHandler{
public void handlePrint();
}
public interface FormatHandler{
public void handleFormat();
}
public class ConcreteHandler
implements HelpHandler,PrintHandler,FormatHandlet{
private HelpHandler helpSuccessor;
private PrintHandler printSuccessor;
private FormatHandler formatSuccessor;
public ConcreteHandler(HelpHandler helpSuccessor,PrintHandler
printSuccessor,FormatHandler formatSuccessor)
{
this.helpSuccessor=helpSuccessor;
this.printSuccessor=printSuccessor;
this.formatSuccessor=formatSuccessor;
}
設計模式(Patterns in Java) -- http://www.jdon.com
63
public void handleHelp(){
.......
}
public void handlePrint(){this.printSuccessor=printSuccessor;}
public void handleFormat(){this.formatSuccessor=formatSuccessor;}
}
這個辦法在增加新的請求request 情況下,只是節省了接口的修改量,接口實現
ConcreteHandler 還需要修改。而且代碼顯然不簡單美麗。
解決方案3: 在Handler 接口中只使用一個參數化方法:
public interface Handler{
public void handleRequest(String request);
}
那麼Handler 實現代碼如下:
public class ConcreteHandler implements Handler{
private Handler successor;
public ConcreteHandler(Handler successor){
this.successor=successor;
}
public void handleRequest(String request){
if (request.equals("Help")){
//這裏是處理Help 的具體代碼
}else
//傳遞到下一個
successor.handle(request);
}
}
}
這裏先假設request 是String 類型,如果不是怎麼辦?當然我們可以創建一個專門類
Request
最後解決方案:接口Handler 的代碼如下:
public interface Handler{
public void handleRequest(Request request);
}
Request 類的定義:
設計模式(Patterns in Java) -- http://www.jdon.com
64
public class Request{
private String type;
public Request(String type){this.type=type;}
public String getType(){return type;}
public void execute(){
//request 真正具體行爲代碼
}
}
那麼Handler 實現代碼如下:
public class ConcreteHandler implements Handler{
private Handler successor;
public ConcreteHandler(Handler successor){
this.successor=successor;
}
public void handleRequest(Request request){
if (request instanceof HelpRequest){
//這裏是處理Help 的具體代碼
}else if (request instanceof PrintRequst){
request.execute();
}else
//傳遞到下一個
successor.handle(request);
}
}
}
這個解決方案就是CoR, 在一個鏈上,都有相應職責的類,因此叫Chain of Responsibility.
CoR 的優點:
因爲無法預知來自外界的請求是屬於哪種類型,每個類如果碰到它不能處理的請求只要放棄
就可以。無疑這降低了類之間的耦合性。
缺點是效率低,因爲一個請求的完成可能要遍歷到最後纔可能完成,當然也可以用樹的概念
優化。在Java AWT1.0 中,對於鼠標按鍵事情的處理就是使用CoR,到Java.1.1 以後,就
使用Observer 代替CoR
擴展性差,因爲在CoR 中,一定要有一個統一的接口Handler.侷限性就在這裏。
設計模式(Patterns in Java) -- http://www.jdon.com
65
設計模式之Command
Command 模式是最讓我疑惑的一個模式,我在閱讀了很多代碼後,才感覺隱約掌握其大概原
理,我認爲理解設計模式最主要是掌握起原理構造,這樣纔對自己實際編程有指導作
用.Command 模式實際上不是個很具體,規定很多的模式,正是這個靈活性,讓人有些
confuse.
Command 定義
不少Command 模式的代碼都是針對圖形界面的,它實際就是菜單命令,我們在一個下拉菜單
選擇一個命令時,然後會執行一些動作.
將這些命令封裝成在一個類中,然後用戶(調用者)再對這個類進行操作,這就是Command 模
式,換句話說,本來用戶(調用者)是直接調用這些命令的,如菜單上打開文檔(調用者),就直
接指向打開文檔的代碼,使用Command 模式,就是在這兩者之間增加一箇中間者,將這種直接
關係拗斷,同時兩者之間都隔離,基本沒有關係了.
顯然這樣做的好處是符合封裝的特性,降低耦合度,Command 是將對行爲進行封裝的典型模
式,Factory 是將創建進行封裝的模式,
從Command 模式,我也發現設計模式一個"通病":好象喜歡將簡單的問題複雜化, 喜歡在不
同類中增加第三者,當然這樣做有利於代碼的健壯性可維護性還有複用性.
如何使用?
具體的Command 模式代碼各式各樣,因爲如何封裝命令,不同系統,有不同的做法.下面事例
是將命令封裝在一個Collection 的List 中,任何對象一旦加入List 中,實際上裝入了一個
封閉的黑盒中,對象的特性消失了,只有取出時,纔有可能模糊的分辨出:
典型的Command 模式需要有一個接口.接口中有一個統一的方法,這就是"將命令/請求封裝
爲對象":
public interface Command {
public abstract void execute ( );
}
具體不同命令/請求代碼是實現接口Command,下面有三個具體命令
public class Engineer implements Command {
public void execute( ) {
//do Engineer's command
}
}
public class Programmer implements Command {
設計模式(Patterns in Java) -- http://www.jdon.com
66
public void execute( ) {
//do programmer's command
}
}
public class Politician implements Command {
public void execute( ) {
//do Politician's command
}
}
按照通常做法,我們就可以直接調用這三個Command,但是使用Command 模式,我們要將他們
封裝起來,扔到黑盒子List 裏去:
public class producer{
public static List produceRequests() {
List queue = new ArrayList();
queue.add( new DomesticEngineer() );
queue.add( new Politician() );
queue.add( new Programmer() );
return queue;
}
}
這三個命令進入List 中後,已經失去了其外表特徵,以後再取出,也可能無法分辨出誰是
Engineer 誰是Programmer 了,看下面如何調用Command 模式:
public class TestCommand {
public static void main(String[] args) {
List queue = Producer.produceRequests();
for (Iterator it = queue.iterator(); it.hasNext(); )
//取出List 中東東,其他特徵都不能確定,只能保證一
個特徵是100%正確,
// 他們至少是接口Command 的"兒子".所以強制轉換
類型爲接口Command
((Command)it.next()).execute();
設計模式(Patterns in Java) -- http://www.jdon.com
67
}
}
由此可見,調用者基本只和接口打交道,不合具體實現交互,這也體現了一個原則,面向接口
編程,這樣,以後增加第四個具體命令時,就不必修改調用者TestCommand 中的代碼了.
理解了上面的代碼的核心原理,在使用中,就應該各人有自己方法了,特別是在如何分離調用
者和具體命令上,有很多實現方法,上面的代碼是使用"從List 過一遍"的做法.這種做法只
是爲了演示.
使用Command 模式的一個好理由還因爲它能實現Undo 功能.每個具體命令都可以記住它剛剛
執行的動作,並且在需要時恢復.
Command 模式在界面設計中應用廣泛.Java 的Swing 中菜單命令都是使用Command 模式,由於
Java 在界面設計的性能上還有欠缺,因此界面設計具體代碼我們就不討論,網絡上有很多這
樣的示例.
參考:
http://www.patterndepot.com/put/8/command.pdf
設計模式(Patterns in Java) -- http://www.jdon.com
68
設計模式之State
State 的定義: 不同的狀態,不同的行爲;或者說,每個狀態有着相應的行爲.
何時使用?
State 模式在實際使用中比較多,適合"狀態的切換".因爲我們經常會使用If elseif else
進行狀態切換, 如果針對狀態的這樣判斷切換反覆出現,我們就要聯想到是否可以採取
State 模式了.
不只是根據狀態,也有根據屬性.如果某個對象的屬性不同,對象的行爲就不一樣,這點在數
據庫系統中出現頻率比較高,我們經常會在一個數據表的尾部,加上property 屬性含義的字
段,用以標識記錄中一些特殊性質的記錄,這種屬性的改變(切換)又是隨時可能發生的,就有
可能要使用State.
是否使用?
在實際使用,類似開關一樣的狀態切換是很多的,但有時並不是那麼明顯,取決於你的經驗和
對系統的理解深度.
這裏要闡述的是"開關切換狀態" 和" 一般的狀態判斷"是有一些區別的, " 一般的狀態判
斷"也是有if..elseif 結構,例如:
if (which==1) state="hello";
else if (which==2) state="hi";
else if (which==3) state="bye";
這是一個" 一般的狀態判斷",state 值的不同是根據which 變量來決定的,which 和state
沒有關係.如果改成:
if (state.euqals("bye")) state="hello";
else if (state.euqals("hello")) state="hi";
else if (state.euqals("hi")) state="bye";
這就是"開關切換狀態",是將state 的狀態從"hello"切換到"hi",再切換到""bye";在切換
到"hello",好象一個旋轉開關,這種狀態改變就可以使用State 模式了.
如果單純有上面一種將"hello"-->"hi"-->"bye"-->"hello"這一個方向切換,也不一定需要
使用State 模式,因爲State 模式會建立很多子類,複雜化,但是如果又發生另外一個行爲:
將上面的切換方向反過來切換,或者需要任意切換,就需要State 了.
請看下例:
設計模式(Patterns in Java) -- http://www.jdon.com
69
public class Context{
private Color state=null;
public void push(){
//如果當前red 狀態就切換到blue
if (state==Color.red) state=Color.blue;
//如果當前blue 狀態就切換到green
else if (state==Color.blue) state=Color.green;
//如果當前black 狀態就切換到red
else if (state==Color.black) state=Color.red;
//如果當前green 狀態就切換到black
else if (state==Color.green) state=Color.black;
Sample sample=new Sample(state);
sample.operate();
}
public void pull(){
//與push 狀態切換正好相反
if (state==Color.green) state=Color.blue;
else if (state==Color.black) state=Color.green;
else if (state==Color.blue) state=Color.red;
else if (state==Color.red) state=Color.black;
Sample2 sample2=new Sample2(state);
sample2.operate();
}
}
在上例中,我們有兩個動作push 推和pull 拉,這兩個開關動作,改變了Context 顏色,至此,
我們就需要使用State 模式優化它.
另外注意:但就上例,state 的變化,只是簡單的顏色賦值,這個具體行爲是很簡單的,State
適合巨大的具體行爲,因此在,就本例,實際使用中也不一定非要使用State 模式,這會增加
子類的數目,簡單的變複雜.
例如: 銀行帳戶, 經常會在Open 狀態和Close 狀態間轉換.
設計模式(Patterns in Java) -- http://www.jdon.com
70
例如: 經典的TcpConnection, Tcp 的狀態有創建偵聽關閉三個,並且反覆轉換,其創建偵
聽關閉的具體行爲不是簡單一兩句就能完成的,適合使用State
例如:信箱POP 帳號, 會有四種狀態, start HaveUsername Authorized quit,每個狀態對應
的行爲應該是比較大的.適合使用State
例如:在工具箱挑選不同工具,可以看成在不同工具中切換,適合使用State.如具體繪圖程
序,用戶可以選擇不同工具繪製方框直線曲線,這種狀態切換可以使用State.
如何使用
State 需要兩種類型實體參與:
1.state manager 狀態管理器,就是開關,如上面例子的Context 實際就是一個state
manager, 在state manager 中有對狀態的切換動作.
2.用抽象類或接口實現的父類,,不同狀態就是繼承這個父類的不同子類.
以上面的Context 爲例.我們要修改它,建立兩個類型的實體.
第一步: 首先建立一個父類:
public abstract class State{
public abstract void handlepush(Context c);
public abstract void handlepull(Context c);
public abstract void getcolor();
}
父類中的方法要對應state manager 中的開關行爲,在state manager 中本例就是Context
中,有兩個開關動作push 推和pull 拉.那麼在狀態父類中就要有具體處理這兩個動
作:handlepush() handlepull(); 同時還需要一個獲取push 或pull結果的方法getcolor()
下面是具體子類的實現:
public class BlueState extends State{
public void handlepush(Context c){
//根據push 方法"如果是blue 狀態的切換到green" ;
c.setState(new GreenState());
}
public void handlepull(Context c){
//根據pull 方法"如果是blue 狀態的切換到red" ;
設計模式(Patterns in Java) -- http://www.jdon.com
71
c.setState(new RedState());
}
public abstract void getcolor(){ return (Color.blue)}
}
同樣其他狀態的子類實現如blue 一樣.
第二步: 要重新改寫State manager 也就是本例的Context:
public class Context{
private Sate state=null; //我們將原來的Color state 改成了新建的State
state;
//setState 是用來改變state 的狀態使用setState 實現狀態的切換
pulic void setState(State state){
this.state=state;
}
public void push(){
//狀態的切換的細節部分,在本例中是顏色的變化,已經封裝在子類的
handlepush 中實現,這裏無需關心
state.handlepush(this);
//因爲sample 要使用state 中的一個切換結果,使用getColor()
Sample sample=new Sample(state.getColor());
sample.operate();
}
public void pull(){
state.handlepull(this);
Sample2 sample2=new Sample2(state.getColor());
設計模式(Patterns in Java) -- http://www.jdon.com
72
sample2.operate();
}
}
至此,我們也就實現了State 的refactorying 過程.
以上只是相當簡單的一個實例,在實際應用中,handlepush 或handelpull 的處理是複雜的.
參考資源:
the State and Stategy
How to implement state-dependent behavior
The state patterns
設計模式(Patterns in Java) -- http://www.jdon.com
73
設計模式之Strategy(策略)
Strategy 是屬於設計模式中對象行爲型模式,主要是定義一系列的算法,把這些算法一個
個封裝成單獨的類.
Stratrgy 應用比較廣泛,比如, 公司經營業務變化圖, 可能有兩種實現方式,一個是線條曲
線,一個是框圖(bar),這是兩種算法,可以使用Strategy 實現.
這裏以字符串替代爲例, 有一個文件,我們需要讀取後,希望替代其中相應的變量,然後輸出.
關於替代其中變量的方法可能有多種方法,這取決於用戶的要求,所以我們要準備幾套變量
字符替代方案.
首先,我們建立一個抽象類RepTempRule 定義一些公用變量和方法:
public abstract class RepTempRule{
protected String oldString="";
public void setOldString(String oldString){
this.oldString=oldString;
}
protected String newString="";
設計模式(Patterns in Java) -- http://www.jdon.com
74
public String getNewString(){
return newString;
}
public abstract void replace() throws Exception;
}
在RepTempRule 中有一個抽象方法abstract 需要繼承明確,這個replace 裏其實是替代的
具體方法.
我們現在有兩個字符替代方案,
1.將文本中aaa 替代成bbb;
2.將文本中aaa 替代成ccc;
對應的類分別是RepTempRuleOne RepTempRuleTwo
public class RepTempRuleOne extends RepTempRule{
public void replace() throws Exception{
//replaceFirst 是jdk1.4 新特性
newString=oldString.replaceFirst("aaa", "bbbb")
System.out.println("this is replace one");
}
}
public class RepTempRuleTwo extends RepTempRule{
public void replace() throws Exception{
newString=oldString.replaceFirst("aaa", "ccc")
System.out.println("this is replace Two");
}
設計模式(Patterns in Java) -- http://www.jdon.com
75
}
第二步:我們要建立一個算法解決類,用來提供客戶端可以自由選擇算法。
public class RepTempRuleSolve {
private RepTempRule strategy;
public RepTempRuleSolve(RepTempRule rule){
this.strategy=rule;
}
public String getNewContext(Site site,String oldString) {
return strategy.replace(site,oldString);
}
public void changeAlgorithm(RepTempRule newAlgorithm) {
strategy = newAlgorithm;
}
}
調用如下:
public class test{
......
public void testReplace(){
//使用第一套替代方案
RepTempRuleSolve solver=new RepTempRuleSolve(new RepTempRuleSimple());
solver.getNewContext(site,context);
//使用第二套
solver=new RepTempRuleSolve(new RepTempRuleTwo());
solver.getNewContext(site,context);

設計模式(Patterns in Java) -- http://www.jdon.com
77
設計模式之Mediator(中介者)
Mediator 定義:
用一箇中介對象來封裝一系列關於對象交互行爲.
爲何使用Mediator?
各個對象之間的交互操作非常多;每個對象的行爲操作都依賴彼此對方,修改一個對象的行
爲,同時會涉及到修改很多其他對象的行爲,如果使用Mediator 模式,可以使各個對象間的
耦合鬆散,只需關心和Mediator 的關係,使多對多的關係變成了一對多的關係,可以降低系
統的複雜性,提高可修改擴展性.
如何使用?
首先有一個接口,用來定義成員對象之間的交互聯繫方式:
public interface Mediator { }
Meiator 具體實現,真正實現交互操作的內容:
public class ConcreteMediator implements Mediator {
//假設當前有兩個成員.
private ConcreteColleague1 colleague1 = new ConcreteColleague1();
private ConcreteColleague2 colleague2 = new ConcreteColleague2();
...
}
再看看另外一個參與者:成員,因爲是交互行爲,都需要雙方提供一些共同接口,這種要求在
Visitor Observer 等模式中都是相同的.
public class Colleague {
private Mediator mediator;
public Mediator getMediator() {
return mediator;
}
public void setMediator( Mediator mediator ) {
this.mediator = mediator;
}
設計模式(Patterns in Java) -- http://www.jdon.com
78
}
public class ConcreteColleague1 { }
public class ConcreteColleague2 { }
每個成員都必須知道Mediator,並且和Mediator 聯繫,而不是和其他成員聯繫.
至此,Mediator 模式框架完成,可以發現Mediator 模式規定不是很多,大體框架也比較簡單,
但實際使用起來就非常靈活.
Mediator 模式在事件驅動類應用中比較多,例如界面設計GUI.;聊天,消息傳遞等,在聊天應
用中,需要有一個MessageMediator,專門負責request/reponse 之間任務的調節.
MVC 是J2EE 的一個基本模式,View Controller 是一種Mediator,它是Jsp 和服務器上應用
程序間的Mediator.
設計模式(Patterns in Java) -- http://www.jdon.com
79
設計模式之Interpreter(解釋器)
Interpreter 定義:
定義語言的文法,並且建立一個解釋器來解釋該語言中的句子.
Interpreter 似乎使用面不是很廣,它描述了一個語言解釋器是如何構成的,在實際應用中,
我們可能很少去構造一個語言的文法.我們還是來簡單的瞭解一下:
首先要建立一個接口,用來描述共同的操作.
public interface AbstractExpression {
void interpret( Context context );
}
再看看包含解釋器之外的一些全局信息
public interface Context { }
AbstractExpression 的具體實現分兩種:終結符表達式和非終結符表達式:
public class TerminalExpression implements AbstractExpression {
public void interpret( Context context ) { }
}
對於文法中沒一條規則,非終結符表達式都必須的:
public class NonterminalExpression implements AbstractExpression {
private AbstractExpression successor;
public void setSuccessor( AbstractExpression successor ) {
this.successor = successor;
}
public AbstractExpression getSuccessor() {
return successor;
}
public void interpret( Context context ) { }
}
設計模式(Patterns in Java) -- http://www.jdon.com
80
設計模式之Visitor
Visitor 定義
作用於某個對象羣中各個對象的操作. 它可以使你在不改變這些對象本身的情況下,定義作
用於這些對象的新操作.
在Java 中,Visitor 模式實際上是分離了collection 結構中的元素和對這些元素進行操作
的行爲.
爲何使用Visitor?
Java 的Collection(包括Vector 和Hashtable)是我們最經常使用的技術,可是Collection
好象是個黑色大染缸,本來有各種鮮明類型特徵的對象一旦放入後,再取出時,這些類型就消
失了.那麼我們勢必要用If 來判斷,如:
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Collection)
messyPrintCollection((Collection)o);
else if (o instanceof String)
System.out.println("'"+o.toString()+"'");
else if (o instanceof Float)
System.out.println(o.toString()+"f");
else
System.out.println(o.toString());
}
在上例中,我們使用了instanceof 來判斷o 的類型.
很顯然,這樣做的缺點代碼If else if 很繁瑣.我們就可以使用Visitor 模式解決它.
如何使用Visitor?
針對上例,我們設計一個接口visitor 訪問者:
public interface Visitor
{
public void visitCollection(Collection collection);
public void visitString(String string);
public void visitFloat(Float float);
}
在這個接口中,將我們認爲Collection 有可能的類的類型放入其中.
設計模式(Patterns in Java) -- http://www.jdon.com
81
有了訪問者,我們需要被訪問者,被訪問者就是我們Collection 的每個元素Element,我們要
爲這些Element 定義一個可以接受訪問的接口(訪問和被訪問是互動的,只有訪問者,被訪問
者如果表示不歡迎,訪問者就不能訪問),
我們定義這個接口叫Visitable,用來定義一個Accept 操作,也就是說讓Collection 每個元
素具備可訪問性.
public interface Visitable
{
public void accept(Visitor visitor);
}
好了,有了兩個接口,我們就要定義他們的具體實現(Concrete class):
public class ConcreteElement implements Visitable
{
private String value;
public ConcreteElement(String string) {
value = string;
}
//定義accept 的具體內容這裏是很簡單的一句調用
public void accept(Visitor visitor) {
visitor.visitString(this);
}
}
再看看訪問者的Concrete 實現:
public class ConcreteVisitor implements Visitor
{
//在本方法中,我們實現了對Collection 的元素的成功訪問
public void visitCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Visitable)
((Visitable)o).accept(this);
}
public void visitString(String string) {
System.out.println("'"+string+"'");
}
設計模式(Patterns in Java) -- http://www.jdon.com
82
public void visitFloat(Float float) {
System.out.println(float.toString()+"f");
}
}
在上面的visitCollection 我們實現了對Collection 每個元素訪問,只使用了一個判斷語句,
只要判斷其是否可以訪問.
至此,我們完成了Visitor 模式基本架構.
使用Visitor 模式的前提
對象羣結構中(Collection) 中的對象類型很少改變,也就是說訪問者的身份類型很少改變,
如上面中Visitor 中的類型很少改變,如果需要增加新的操作,比如上例中我們在
ConcreteElement 具體實現外,還需要新的ConcreteElement2 ConcreteElement3.
可見使用Visitor 模式是有前提的,在兩個接口Visitor 和Visitable 中,確保Visitor 很少
變化,變化的是Visitable,這樣使用Visitor 最方便.
如果Visitor 也經常變化, 也就是說,對象羣中的對象類型經常改變,一般建議是,不如在這
些對象類中逐個定義操作.但是Java 的Reflect 技術解決了這個問題.
Reflect 技術是在運行期間動態獲取對象類型和方法的一種技術,具體實現參考Javaworld
的英文原文.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章