面向?qū)ο蠓治雠c設(shè)計第九章_第1頁
面向?qū)ο蠓治雠c設(shè)計第九章_第2頁
面向?qū)ο蠓治雠c設(shè)計第九章_第3頁
面向?qū)ο蠓治雠c設(shè)計第九章_第4頁
面向?qū)ο蠓治雠c設(shè)計第九章_第5頁
已閱讀5頁,還剩88頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1、面向?qū)ο蠓治雠c設(shè)計2第九章 設(shè)計模式 q設(shè)計模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的,設(shè)計模式使代碼編制真正工程化,設(shè)計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。q GoF的“設(shè)計模式”是第一次將設(shè)計模式提升到理論高度,并將之規(guī)范化, GoF提出了23種基本設(shè)計模式,自此,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過程中,新的大量的設(shè)計模式不斷出現(xiàn)。3GoFRalph JohnsonRichard HelmErich Gamma

2、John Vlissides4設(shè)計模式q 一、設(shè)計模式和框架一、設(shè)計模式和框架 q 現(xiàn)在,可復(fù)用面向?qū)ο筌浖到y(tǒng)現(xiàn)在一般劃分為三大類:應(yīng)用程序工具箱和框架(Framework),我們平時開發(fā)的具體軟件都是應(yīng)用程序;Java的API屬于工具箱;而框架是構(gòu)成一類特定軟件可復(fù)用設(shè)計的一組相互協(xié)作的類。EJB(EnterpriseJavaBeans)是Java應(yīng)用于企業(yè)計算的框架.q 框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)類和對象的關(guān)系等等設(shè)計參數(shù),以便于具體應(yīng)用實現(xiàn)者能集中精力于應(yīng)用本身的特定細(xì)節(jié)。框架主要記錄軟件應(yīng)用中共同的設(shè)計決策,框架強調(diào)設(shè)計復(fù)用,因此框架設(shè)計中必然要使用設(shè)計模式.5設(shè)計模式q另外,

3、設(shè)計模式有助于對框架結(jié)構(gòu)的理解,成熟的框架通常使用了多種設(shè)計模式,如果你熟悉這些設(shè)計模式,毫無疑問,你將迅速掌握框架的結(jié)構(gòu),我們一般開發(fā)者如果突然接觸EJB, J2EE等框架,會覺得特別難學(xué),難掌握,那么轉(zhuǎn)而先掌握設(shè)計模式,無疑是給了你剖析EJB或J2EE系統(tǒng)的一把利器。6設(shè)計模式q二、設(shè)計模式的原則二、設(shè)計模式的原則 q近年來,大家都開始注意設(shè)計模式。那么,到底我們?yōu)槭裁匆迷O(shè)計模式呢?這么多設(shè)計模式為什么要這么設(shè)計呢?q為什么要提倡“Design Pattern”呢?根本原因是為了代碼復(fù)用,增加可維護性。那么怎么才能實現(xiàn)代碼復(fù)用呢?OO界有前輩的幾個原則:“開閉”原則(Open Close

4、d Principal)、里氏代換原則、合成復(fù)用等原則。設(shè)計模式就是實現(xiàn)了這些原則,從而達到了代碼復(fù)用、增加可維護性的目的。 7設(shè)計模式q1、開閉原則 q此原則是由“Bertrand Meyer”提出的。原文是:“Software entities should be open for extension,but closed for modification”。就是說模塊應(yīng)對擴展開放,而對修改關(guān)閉。模塊應(yīng)盡量在不修改原(是“原”,指原來的代碼)代碼的情況下進行擴展。那么怎么擴展呢?q我們看工廠模式factory pattern:8設(shè)計模式q2、里氏代換原則 q里氏代換原則是由“Barbara

5、 Liskov”提出的 q基類出現(xiàn)的地方,子類一定可以出現(xiàn)q如果調(diào)用的是父類的話,那么換成子類也完全可以運行 9設(shè)計模式q3、聚合/組合復(fù)用原則 q盡量使用聚合/組合,而不是繼承達到復(fù)用q就是說要少用繼承,多用聚合/組合關(guān)系來實現(xiàn) q在Java中,應(yīng)盡量針對Interface編程,而非實現(xiàn)類。這樣,更換子類不會影響調(diào)用它方法的代碼。要讓各個類盡可能少的跟別的類聯(lián)系,不要與陌生人說話。這樣,城門失火,才不至于殃及池魚。擴展性和維護性才能提高10設(shè)計模式q4 、依賴倒轉(zhuǎn)原則 q抽象不應(yīng)該依賴與細(xì)節(jié),細(xì)節(jié)應(yīng)當(dāng)依賴與抽象。q要針對接口編程,而不是針對實現(xiàn)編程。q傳遞參數(shù),或者在組合聚合關(guān)系中,盡量引用

6、層次高的類。q主要是在構(gòu)造對象時可以動態(tài)的創(chuàng)建各種具體對象,當(dāng)然如果一些具體類比較穩(wěn)定,就不必在弄一個抽象類做它的父類,這樣有畫蛇添足的感覺11設(shè)計模式q5、單一職責(zé)原則(SRP)q就一個類而言,應(yīng)該僅有一個引起它變化的原因。12設(shè)計模式q 6 、接口隔離原則q為客戶端提供盡可能小的單獨的接口q定制服務(wù)的接口,每一個接口應(yīng)該是一種角色,不多不少,不干不該干的事,該干的事都要干13設(shè)計模式q7、 迪米特法則(LoD)q一個軟件實體與盡可能少的實體相互作用q也叫最少知識原則。q不要和陌生人說話。q如果兩個類不必彼此直接通信,那么這兩個類就不應(yīng)當(dāng)發(fā)生直接的相互作用。如果其中一個類需要調(diào)用另一個類的某

7、個方法的話,可以通過第三者轉(zhuǎn)發(fā)這個調(diào)用。14設(shè)計模式q理解了這些原則,再看設(shè)計模式,只是在具體問題上怎么實現(xiàn)這些原則而已。張無忌學(xué)太極拳,忘記了所有招式,打倒了玄冪二老,所謂心中無招。設(shè)計模式可謂招數(shù),如果先學(xué)通了各種模式,又忘掉了所有模式而隨心所欲,可謂OO之最高境界。15一、創(chuàng)建型模式q Abstract Factory:提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定它們具體的類。q Builder:將一個復(fù)雜對象的構(gòu)件與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表述。q Factory Method:定義一個用于創(chuàng)建對象的接口,讓子類決定將哪一個類實例化。Factory Me

8、thod使一個類的實例化延遲到其子類。16一、創(chuàng)建型模式qPrototype:用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這個原型來創(chuàng)建新的對象。q Singleton:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。17二、結(jié)構(gòu)型模式q Adapter:將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。q Bridge:將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化。q Composite:將對象組合成樹型結(jié)構(gòu)以表示“部分整體”的層次結(jié)構(gòu)。Composite使得客戶對單個對象和復(fù)合對象的使用具有一致性。18二、結(jié)

9、構(gòu)型模式q Decorator:動態(tài)地給一個對象添加一些額外的職責(zé)。就擴展功能而言,Decorator模式比生成子類方式更為靈活。q Facade:為子系統(tǒng)中的一組接口提供一個一致的界面,F(xiàn)acade模式定義了一個高層接口,這個接口使得這一子系統(tǒng)更加容易使用。q Flyweight:運用共享技術(shù)有效地支持大量細(xì)粒度的對象。q Proxy:為其他對象提供一個代理以控制對這個對象的訪問。19三、行為型模式q Chain of Responsibility:為解除請求的發(fā)送者和接受者之間耦合,而使多個對象都有機會處理這個請求。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它。q C

10、ommand:將一個請求封裝為一個對象,從而使你可以用不同的請求對客戶進行參數(shù)化;對請求排隊或記錄請求日志,以及支持可取消的操作。q Interpreter:給定一個語言,定義它的文法的一種表示,并定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。20三、行為型模式q Iterator:提供一種方法順序訪問一個聚合對象中各個元素,而又不需暴露該對象的內(nèi)部表示。q Mediator:用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使器耦合松散,而且可以獨立地改變它們之間的交互。q Memento:在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存

11、這個狀態(tài)。這樣以后就可以將該對象恢復(fù)到保存的狀態(tài)。21三、行為型模式qObserver:定義對象間的一種一對多的依賴關(guān)系,以便當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并自動刷新。q State:允許一個對象在其內(nèi)部狀態(tài)改變時改變它的行為。對象看起來似乎修改了它所屬的類。q Strategy:定義一系列的算法,把它們一個個封裝起來,并且使它們可相互替換。本模式使得算法的變化可獨立于使用它的客戶。22qTemplate Method:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。q

12、 Visitor:表示一個作用于某對象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。23設(shè)計模式之單例模式 q作為對象的創(chuàng)建模式GOF95, 單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。這個類稱為單例類。q單例模式的要點單例模式的要點單例模式的要點有三個;q一是某各類只能有一個實例;q二是它必須自行創(chuàng)建這個實例;q三是它必須自行向整個系統(tǒng)提供這個實例。 24單例模式q 在上面的對象圖中,有一個單例對象,而客戶甲、客戶乙 和客戶丙是單例對象的三個客戶對象??梢钥吹?,所有的客戶對象共享一個單例對象。而且從單例對象到自身的連接線可

13、以看出,單例對象持有對自己的引用。25單例模式q 一些資源管理器常常設(shè)計設(shè)計成單例模式。 q 在計算機系統(tǒng)中,需要管理的資源包括軟件外部資源,譬如每臺計算機可以有若干個打印機,但只能有一個Printer Spooler, 以避免兩個打印作業(yè)同時輸出到打印機中。每臺計算機可以有若干傳真卡,但是只應(yīng)該有一個軟件負(fù)責(zé)管理傳真卡,以避免出現(xiàn)兩份傳真作業(yè)同時傳到傳真卡中的情況。每臺計算機可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。需要管理的資源包括軟件內(nèi)部資源,譬如,大多數(shù)的軟件都有一個(甚至多個)屬性(properties)文件存放系統(tǒng)配置。這樣的系統(tǒng)應(yīng)

14、當(dāng)由一個對象來管理一個屬性文件。 26單例模式q需要管理的軟件內(nèi)部資源也包括譬如負(fù)責(zé)記錄網(wǎng)站來訪人數(shù)的部件,記錄軟件系統(tǒng)內(nèi)部事件、出錯信息的部件,或是對系統(tǒng)的表現(xiàn)進行檢查的部件等。這些部件都必須集中管理,不可政出多頭。這些資源管理器構(gòu)件必須只有一個實例,這是其一;它們必須自行初始化,這是其二;允許整個系統(tǒng)訪問自己這是其三。因此,它們都滿足單例模式的條件,是單例模式的應(yīng)用。 27單例模式q一個例子:一個例子:Windows 回收站回收站q在整個視窗系統(tǒng)中,回收站只能有一個實例,整個系統(tǒng)都使用這個惟一的實例,而且回收站自行提供自己的實例。因此,回收站是單例模式的應(yīng)用。28單例模式q單例模式的結(jié)構(gòu)單

15、例模式的結(jié)構(gòu)雖然單例模式中的單例類被限定只能有一個實例,但是單例模式和單例類可以很容易被推廣到任意且有限多個實例的情況,這時候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class),單例類的簡略類圖如下所示。 29q由于Java 語言的特點,使得單例模式在Java 語言的實現(xiàn)上有自己的特點。這些特點主要表現(xiàn)在單例類如何將自己實例化上。q餓漢式單例類餓漢式單例類是在Java 語言里實現(xiàn)得最為簡便的單例類,下面所示的類圖描述了一個餓漢式單例類的典型實現(xiàn)。30q public class EagerSingleton private static final E

16、agerSingleton m_instance = new EagerSingleton(); /* * 私有的默認(rèn)構(gòu)造子 */ private EagerSingleton() /* * 靜態(tài)工廠方法 */ public static EagerSingleton getInstance() return m_instance; 31q可以看出,在這個類被加載時,靜態(tài)變量m_instance 會被初始化,此時類的私有構(gòu)造子會被調(diào)用。這時候,單例類的惟一實例就被創(chuàng)建出來了。Java 語言中單例類的一個最重要的特點是類的構(gòu)造子是私有的,從而避免外界利用構(gòu)造子直接創(chuàng)建出任意多的實例。值得指出的是

17、,由于構(gòu)造子是私有的,因此,此類不能被繼承。32q懶漢式單例類懶漢式單例類 q與餓漢式單例類相同之處是,類的構(gòu)造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時將自己實例化。如果加載器是靜態(tài)的,那么在懶漢式單例類被加載時不會將自己實例化。如下圖所示,類圖中給出了一個典型的餓漢式單例類實現(xiàn)。33qpackage com.javapatterns.singleton.demos; public class LazySingleton private static LazySingleton m_instance = null; /* * 私有的默認(rèn)構(gòu)造子,保證外界無法直接實例化 *

18、/ private LazySingleton() /* * 靜態(tài)工廠方法,返還此類的惟一實例 */ synchronized public static LazySingleton getInstance() if (m_instance = null) m_instance = new LazySingleton(); return m_instance; 34q 讀者可能會注意到,在上面給出懶漢式單例類實現(xiàn)里對靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。有些設(shè)計設(shè)計師在這里建議使用所謂的雙重檢查成例。必須指出的是,雙重檢查成例不可以在Java 語言中使用。不十分熟悉的讀者,可以看看后面給

19、出的小節(jié)。同樣,由于構(gòu)造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時就將自己實例化。即便加載器是靜態(tài)的,在餓漢式單例類被加載時仍會將自己實例化。單從資源利用效率角度來講,這個比懶漢式單例類稍差些。35q 從速度和反應(yīng)時間角度來講,則比懶漢式單例類稍好些。然而,懶漢式單例類在實例化時, 必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當(dāng)單例類作為資源控制器,在實例化時必然涉及資源初始化,而資源初始化很有可能耗費時間。這意味著出現(xiàn)多線程同時首次引用此類的機率變得較大。餓漢式單例類可以在Java 語言內(nèi)實現(xiàn), 但不易在C+ 內(nèi)實現(xiàn),因為靜態(tài)初始化在C+ 里沒有固定的順序,

20、因而靜態(tài)的m_instance 變量的初始化與類的加載順序沒有保證,可能會出問題。這就是為什么GoF 在提出單例類的概念時,舉的例子是懶漢式的。他們的書影響之大,以致Java 語言中單例類的例子也大多是懶漢式的。實際上,本書認(rèn)為餓漢式單例類更符合Java 語言本身的特點。 36q登記式單例類登記式單例類登記式單例類是GoF 為了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點而設(shè)計設(shè)計的。本書把他們的例子翻譯為Java 語言,并將它自己實例化的方式從懶漢式改為餓漢式。只是它的子類實例化的方式只能是懶漢式的, 這是無法改變的。如下圖所示是登記式單例類的一個例子,圖中的關(guān)系線表明,此類已將自己實例化

21、。37q import java.util.HashMap; public class RegSingleton static private HashMap m_registry = new HashMap(); static RegSingleton x = new RegSingleton(); m_registry.put( x.getClass().getName() , x); /* * 保護的默認(rèn)構(gòu)造子 */ protected RegSingleton() /* * 靜態(tài)工廠方法,返還此類惟一的實例 */ static public RegSingleton getInstan

22、ce(String name) if (name = null) name = com.javapatterns.singleton.demos.RegSingleton; if (m_registry.get(name) = null) 38q try m_registry.put( name, Class.forName(name).newInstance() ) ; catch(Exception e) System.out.println(Error happened.); return (RegSingleton) (m_registry.get(name) ); /* * 一個示意

23、性的商業(yè)方法 */ public String about() return Hello, I am RegSingleton.; 39q它的子類RegSingletonChild 需要父類的幫助才能實例化。下圖所示是登記式單例類子類的一個例子。圖中的關(guān)系表明,此類是由父類將子類實例化的。40q import java.util.HashMap; public class RegSingletonChild extends RegSingleton public RegSingletonChild() /* * 靜態(tài)工廠方法 */ static public RegSingletonChild

24、 getInstance() return (RegSingletonChild) RegSingleton.getInstance( com.javapatterns.singleton.demos.RegSingletonChild ); /* * 一個示意性的商業(yè)方法 */ public String about() return Hello, I am RegSingletonChild.; 41q 在GoF 原始的例子中,并沒有g(shù)etInstance() 方法,這樣得到子類必須調(diào)用的getInstance(String name)方法并傳入子類的名字,因此很不方便。本章在登記式單例類

25、子類的例子里,加入了getInstance() 方法,這樣做的好處是RegSingletonChild 可以通過這個方法,返還自已的實例。而這樣做的缺點是,由于數(shù)據(jù)類型不同,無法在RegSingleton 提供這樣一個方法。由于子類必須允許父類以構(gòu)造子調(diào)用產(chǎn)生實例,因此,它的構(gòu)造子必須是公開的。這樣一來,就等于允許了以這樣方式產(chǎn)生實例而不在父類的登記中。這是登記式單例類的一個缺點。GoF 曾指出,由于父類的實例必須存在才可能有子類的實例,這在有些情況下是一個浪費。這是登記式單例類的另一個缺點。42q在什么情況下使用單例模式在什么情況下使用單例模式使用單例模式有一個很重要的必要條件:在一個系統(tǒng)要

26、求一個類只有一個實例時才應(yīng)當(dāng)使用單例模式。反過來說,如果一個類可以有幾個實例共存,那么就沒有必要使用單例類。但是有經(jīng)驗的讀者可能會看到很多不當(dāng)?shù)厥褂脝卫J降睦?,可見做到上面這一點并不容易,下面就是一些這樣的情況。43q例子一 問:我的一個系統(tǒng)需要一些“全程”變量。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個單例類盛放所有的“全程”變量。請問這樣做對嗎?答:這樣做是違背單例模式的用意的。單例模式只應(yīng)當(dāng)在有真正的單一實例的需求時才可使用。一個設(shè)計設(shè)計得當(dāng)?shù)南到y(tǒng)不應(yīng)當(dāng)有所謂的全程變量,這些變量應(yīng)當(dāng)放到它們所描述的實體所對應(yīng)的類中去。將這些變量從它們所描述的實體類中抽出來, 放到一個不相干的單例類中去,會

27、使得這些變量產(chǎn)生錯誤的依賴關(guān)系和耦合關(guān)系。 44q例子二問:我的一個系統(tǒng)需要管理與數(shù)據(jù)庫的連接。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個單例類包裝一個Connection 對象,并在finalize()方法中關(guān)閉這個Connection 對象。這樣的話,在這個單例類的實例沒有被人引用時,這個finalize() 對象就會被調(diào)用,因此,Connection 對象就會被釋放。這多妙啊。45q答:這樣做是不恰當(dāng)?shù)?。除非有單一實例的需求,不然不要使用單例模式。在這里Connection 對象可以同時有幾個實例共存,不需要是單一實例。單例模式有很多的錯誤使用案例都與此例子相似,它們都是試圖使用單例模式管理共

28、享資源的生命周期,這是不恰當(dāng)?shù)摹?46q單例類的狀態(tài)單例類的狀態(tài) q有狀態(tài)的單例類有狀態(tài)的單例類一個單例類可以是有狀態(tài)的(stateful),一個有狀態(tài)的單例對象一般也是可變(mutable) 單例對象。有狀態(tài)的可變的單例對象常常當(dāng)做狀態(tài)庫(repositary)使用。比如一個單例對象可以持有一個int 類型的屬性,用來給一個系統(tǒng)提供一個數(shù)值惟一的序列號碼,作為某個販賣系統(tǒng)的賬單號碼。當(dāng)然,一個單例類可以持有一個聚集,從而允許存儲多個狀態(tài)。 47q沒有狀態(tài)的單例類沒有狀態(tài)的單例類另一方面,單例類也可以是沒有狀態(tài)的(stateless), 僅用做提供工具性函數(shù)的對象。既然是為了提供工具性函數(shù),也

29、就沒有必要創(chuàng)建多個實例,因此使用單例模式很合適。一個沒有狀態(tài)的單例類也就是不變(Immutable) 單例類; 關(guān)于不變模式,讀者可以參見本書的不變(Immutable )模式一章。48q多個多個JVM 系統(tǒng)的分散式系統(tǒng)系統(tǒng)的分散式系統(tǒng)EJB 容器有能力將一個EJB 的實例跨過幾個JVM 調(diào)用。由于單例對象不是EJB,因此,單例類局限于某一個JVM 中。換言之,如果EJB 在跨過JVM 后仍然需要引用同一個單例類的話,這個單例類就會在數(shù)個JVM 中被實例化,造成多個單例對象的實例出現(xiàn)。一個J2EE應(yīng)用系統(tǒng)可能分布在數(shù)個JVM 中,這時候不一定需要EJB 就能造成多個單例類的實例出現(xiàn)在不同JVM

30、 中的情況。 49q如果這個單例類是沒有狀態(tài)的,那么就沒有問題。因為沒有狀態(tài)的對象是沒有區(qū)別的。但是如果這個單例類是有狀態(tài)的, 那么問題就來了。舉例來說,如果一個單例對象可以持有一個int 類型的屬性,用來給一個系統(tǒng)提供一個數(shù)值惟一的序列號碼,作為某個販賣系統(tǒng)的賬單號碼的話,用戶會看到同一個號碼出現(xiàn)好幾次。在任何使用了EJB、RMI 和JINI 技術(shù)的分散式系統(tǒng)中,應(yīng)當(dāng)避免使用有狀態(tài)的單例模式。50q 多個類加載器多個類加載器 q 同一個JVM 中會有多個類加載器,當(dāng)兩個類加載器同時加載同一個類時,會出現(xiàn)兩個實例。在很多J2EE 服務(wù)器允許同一個服務(wù)器內(nèi)有幾個Servlet 引擎時,每一個引擎

31、都有獨立的類加載器,經(jīng)有不同的類加載器加載的對象之間是絕緣的。比如一個J2EE 系統(tǒng)所在的J2EE 服務(wù)器中有兩個Servlet 引擎:一個作為內(nèi)網(wǎng)給公司的網(wǎng)站管理人員使用;另一個給公司的外部客戶使用。兩者共享同一個數(shù)據(jù)庫,兩個系統(tǒng)都需要調(diào)用同一個單例類。如果這個單例類是有狀態(tài)的單例類的話,那么內(nèi)網(wǎng)和外網(wǎng)用戶看到的單例對象的狀態(tài)就會不同。除非系統(tǒng)有協(xié)調(diào)機制,不然在這種情況下應(yīng)當(dāng)盡量避免使用有狀態(tài)的單例類。 51q一個實用的例子:屬性管理器一個實用的例子:屬性管理器這里給出一個讀取屬性(properties) 文件的單例類,作為單例模式的一個實用的例子。屬性文件如同老式的視窗編程時的.ini 文

32、件,用于存放系統(tǒng)的配置信息。配置信息在屬性文件中以屬性的方式存放,一個屬性就是兩個字符串組成的對子,其中一個字符串是鍵(key),另一個字符串是這個鍵的值(value)。52q大多數(shù)的系統(tǒng)都有一些配置常量,這些常量如果是存儲在程序程序內(nèi)部的,那么每一次修改這些常量都需要重新編譯程序。將這些常量放在配置文件中,系統(tǒng)通過訪問這個配置文件取得配置常量,就可以通過修改配置文件而無需修改程序而達到更改系統(tǒng)配置的目的。系統(tǒng)也可以在配置文件中存儲一些工作環(huán)境信息,這樣在系統(tǒng)重啟時,這些工作信息可以延續(xù)到下一個運行周期中。假定需要讀取的屬性文件就在當(dāng)前目錄中,且文件名為perties

33、 。這個文件中有如下的一些屬性項。 53q屬性文件內(nèi)容node1.item1=How node1.item2=are node2.item1=you node2.item2=doing node3.item1=?q例如,node1.item1 就是一個鍵,而How 就是這個鍵所對應(yīng)的值。54qJava 屬性類屬性類Java 提供了一個工具類,稱做屬性類,可以用來完成Java 屬性和屬性文件的操作。這個屬性類的繼承關(guān)系可以從下面的類圖中看清楚。 55q 屬性類提供了讀取屬性和設(shè)置屬性的各種方法。其中讀取屬性的方法有:. contains(Object value) 、containsKey(Ob

34、ject key): 如果給定的參數(shù)或?qū)傩躁P(guān)鍵字在屬性表中有定義,該方法返回True ,否則返回False。. getProperty(String key)、getProperty(String key, String default) :根據(jù)給定的屬性關(guān)鍵字獲取關(guān)鍵字值。56q . list(PrintStream s) 、list(PrintWriter w) :在輸出流中輸出屬性表內(nèi)容。. size():返回當(dāng)前屬性表中定義的屬性關(guān)鍵字個數(shù)。設(shè)置屬性的方法有:. put(Object key, Object value) :向?qū)傩员碇凶芳訉傩躁P(guān)鍵字和關(guān)鍵字的值。. remove(Obj

35、ect key):從屬性表中刪除關(guān)鍵字。57q從屬性文件加載屬性的方法為load(InputStream inStream),可以從一個輸入流中讀入一個屬性列,如果這個流是來自一個文件的話,這個方法就從文件中讀入屬性。將屬性存入屬性文件的方法有幾個,重要的一個是store(OutputStream out, String header) ,將當(dāng)前的屬性列寫入一個輸出流,如果這個輸出流是導(dǎo)向一個文件的,那么這個方法就將屬性流存入文件。58q為什么需要使用單例模式為什么需要使用單例模式屬性是系統(tǒng)的一種資源,應(yīng)當(dāng)避免有多余一個的對象讀取特別是存儲屬性。此外,屬性的讀取可能會在很多地方發(fā)生,創(chuàng)建屬性對

36、象的地方應(yīng)當(dāng)在哪里不是很清楚。換言之,屬性管理器應(yīng)當(dāng)自己創(chuàng)建自己的實例,并且自己向系統(tǒng)全程提供這一事例。因此,屬性文件管理器應(yīng)當(dāng)是一個單例模式負(fù)責(zé)。59q系統(tǒng)系統(tǒng)設(shè)計設(shè)計系統(tǒng)的核心是一個屬性管理器,也就是一個叫做ConfigManager 的類,這個類應(yīng)當(dāng)是一個單例類。因此,這個類應(yīng)當(dāng)有一個靜態(tài)工廠方法,不妨叫做getInstance(), 用于提供自己的實例。為簡單起見,本文在這里采取餓漢方式實現(xiàn)ConfigManager 。例子的類圖如下所示。 6061q import java.util.Properties; import java.io.FileInputStream; import

37、 java.io.File; public class ConfigManager /* * 屬性文件全名 */private static final String PFILE = System.getProperty(user.dir) + File.Separator + Sperties; /* * 對應(yīng)于屬性文件的文件對象變量 */ private File m_file = null; 62q /* * 屬性文件的最后修改日期 */ private long m_lastModifiedTime = 0; /* * 屬性文件所對應(yīng)的屬性對象變量 */ pri

38、vate Properties m_props = null; /* * 本類可能存在的惟一的一個實例 */ private static ConfigManager m_instance = new ConfigManager(); 63q /* * 私有的構(gòu)造子,用以保證外界無法直接實例化 */ private ConfigManager() m_file = new File(PFILE); m_lastModifiedTime = m_file.lastModified(); if(m_lastModifiedTime = 0) System.err.println(PFILE + f

39、ile does not exist!); m_props = new Properties(); 64qtry m_props.load(new FileInputStream(PFILE); catch(Exception e) e.printStackTrace(); 65q /* * 靜態(tài)工廠方法 * return 返還ConfigManager 類的單一實例 */ synchronized public static ConfigManager getInstance() return m_instance; /* * 讀取一特定的屬性項 * * param name 屬性項的項名

40、* param defaultVal 屬性項的默認(rèn)值 * return 屬性項的值(如此項存在), 默認(rèn)值(如此項不存在) */ 66qfinal public Object getConfigItem( String name, Object defaultVal) long newTime = m_file.lastModified(); / 檢查屬性文件是否被其他程序程序 / (多數(shù)情況是程序員手動)修改過 / 如果是,重新讀取此文件if(newTime = 0) / 屬性文件不存在 if(m_lastModifiedTime = 0) System.err.println(PFILE

41、+ file does not exist!); else System.err.println(PFILE + file was deleted!); return defaultVal; 67qelse if(newTime m_lastModifiedTime) / Get rid of the old properties m_props.clear(); try m_props.load(new FileInputStream(PFILE); catch(Exception e) e.printStackTrace(); m_lastModifiedTime = newTime; O

42、bject val = m_props.getProperty(name); if( val = null ) return defaultVal; else return val; 68q在上面直接使用了一個局域的常量儲存儲屬性文件的路徑。在實際的系統(tǒng)中,讀者可以采取更靈活的方式將屬性文件的路徑傳入。讀者可以看到,這個管理器類有一個很有意思的功能,即在每一次調(diào)用時,檢查屬性文件是否已經(jīng)被更新過。如果確實已經(jīng)被更新過的話,管理器會自動重新加載屬性文件, 從而保證管理器的內(nèi)容與屬性文件的內(nèi)容總是一致的。69q怎樣調(diào)用屬性管理器怎樣調(diào)用屬性管理器下面的源代碼演示了怎樣調(diào)用ConfigManager

43、 來讀取屬性文件。70q BufferedReader reader = new BufferedReader( new InputStreamReader(System.in); System.out.println(Type quit to quit); do System.out.print(Property item to read: ); String line = reader.readLine(); if(line.equals(quit) break; System.out.println(ConfigManager.getInstance() .getConfigItem(l

44、ine, Not found.); while(true); 71q Java 語言中的單例模式語言中的單例模式Java 語言中就有很多的單例模式的應(yīng)用實例,這里討論比較有名的幾個。Java 的的Runtime 對象對象在Java 語言內(nèi)部,java.lang.Runtime 對象就是一個使用單例模式的例子。在每一個Java 應(yīng)用程序程序里面,都有惟一的一個Runtime 對象。通過這個Runtime 對象,應(yīng)用程序可以與其運行環(huán)境發(fā)生相互作用。Runtime 類提供一個靜態(tài)工廠方法getRuntime():: q public static Runtime getRuntime();72q

45、通過調(diào)用此方法,可以獲得Runtime 類惟一的一個實例: Runtime rt = Runtime getRuntime(); Runtime 對象通常的用途包括:執(zhí)行外部命令;返回現(xiàn)有內(nèi)存即全部內(nèi)存;運行垃圾收集器;加載動態(tài)庫等。下面的例子演示了怎樣使用Runtime 對象運行一個外部程序。代碼清單8:怎樣使用Runtime 對象運行一個外部命令73qimport java.io.*; public class CmdTest public static void main(String args) throws IOException Process proc = Runtime.get

46、Runtime().exec(notepad.exe); 74q 上面的程序在運行時會打開notepad 程序。應(yīng)當(dāng)指出的是,在Windows 2000 的環(huán)境中,如果需要打開一個Word 文件,而又不想指明Word 軟件軟件安裝的位置時,可以使用下面的做法: q Process proc = Runtime.getRuntime().exec( cmd /E:ON /c start MyDocument.doc); q 在上面,被執(zhí)行的命令是start MyDocument.doc ,開關(guān)E:ON 指定DOS 命令處理器允許命令擴展,而開關(guān)/C 指明后面跟隨的字符串是命令,并在執(zhí)行命令后關(guān)閉

47、DOS 窗口,start 命令會開啟一個單獨的窗口執(zhí)行所提供的命令。75qIntrospector 類類一般的應(yīng)用程序可能永遠也不會直接用到Introspector 類,但讀者應(yīng)該知道Introspector 是做什么的。Sun 提供了一個叫做BeanBox 的系統(tǒng),允許動態(tài)地加載JavaBean ,并動態(tài)地修改其性質(zhì)。BeanBox 在運行時的情況如下圖所示。7677q 在上面的圖中顯示了BeanBox 最重要的兩個視窗,一個叫做BeanBox 視窗,另一個叫做性質(zhì)視窗。在上面的BeanBox 視窗中顯示了一個Juggler Bean 被放置到視窗中的情況。相應(yīng)的,在性質(zhì)視窗中顯示了Jugg

48、ler Bean 的所有性質(zhì)。所有的Java 集成環(huán)境都提供這種功能,這樣的系統(tǒng)就叫做BeanBox 系統(tǒng)。BeanBox 系統(tǒng)使用一種自?。↖ntrospection )過程來確定一個Bean 所輸出的性質(zhì)、事件和方法。這個自省機制是通過自省者類,也即java.util.Introspector 類實現(xiàn)的;這個機制是建立在Java 反射(Reflection) 機制和命名規(guī)范的基礎(chǔ)之上的。比如,Introspector 類可以確定Juggler Bean 所支持的所有的性質(zhì),這是因為Introspector 類可以得到所有的方法,然后將其中的取值和賦值方法以及它們的特征加以比較,從而得出結(jié)果

49、。顯然,在整個BeanBox 系統(tǒng)中只需要一個Introspector 對象,下面所示就是這個類的結(jié)構(gòu)圖。78q 可以看出,Introspector 類的構(gòu)造子是私有的, 一個靜態(tài)工廠方法instantiate() 提供了Instrospector 類的惟一實例。換言之,這個類是單例模式的應(yīng)用。q java.awt.Toolkit 類類Toolkit 類是一個非常有趣的單例模式的例子。Toolkit 使用單例模式創(chuàng)建所謂的Toolkit 的默認(rèn)對象,并且確保這個默認(rèn)實例在整個系統(tǒng)中是惟一的。Toolkit 類提供了一個靜態(tài)的方法getDefaultToolkit() 來提供這個惟一的實例,這個

50、方法相當(dāng)于懶漢式的單例方法,因此整個方法都是同步化的。79q代碼清單9:getDefaultToolkit() 方法 public static synchronized Toolkit getDefaultToolkit() . 80q其中性質(zhì)defaultToolkit 實際上就是靜態(tài)的getDefaultToolkit 類。有趣的是,由于Toolkit 是一個抽象類,因此其子類如果提供一個私有的構(gòu)造子,那么其子類便是一個正常的單例類;而如果其子類作為具體實現(xiàn)提供一個公開的構(gòu)造子, 這時候這個具體子類便是 不完全的單例類。關(guān)于不完全的單例類的討論請見本章后面的專題:不完全的單例類一節(jié) 81

51、q 模版方法模式模版方法模式同時,熟悉模版方法模式的讀者可以看出,getDefaultToolkit() 方法實際上是一個模版方法。私有構(gòu)造子是推遲到子類實現(xiàn)的剩余邏輯,根據(jù)子類對這個剩余邏輯的不同實現(xiàn), 子類就可以提供完全不同的行為。對Toolkit 的子類而言,私有構(gòu)造子依賴于操作系統(tǒng)操作系統(tǒng),不同的子類可以根據(jù)不同的操作系統(tǒng)而給出不同的邏輯,從而使Toolkit 的子類對不同的操作系統(tǒng)給出不同的行為。javax.swing.TimerQueue 類類這是一個不完全的單例類,由于這個類是在Swing 的定時器類中使用的,因此我們將在后面介紹。 82q不完全的單例類不完全的單例類 什么是不完全的單例類估計有些讀者見過下面這樣的“不完全”的單例類。83qpackage com.javapatterns.singleton.demos; public class LazySingleton private static LazySinglet

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論