版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、面向?qū)ο蠓治雠c設(shè)計(jì)2第九章 設(shè)計(jì)模式 q設(shè)計(jì)模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過(guò)分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無(wú)疑問(wèn),設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的,設(shè)計(jì)模式使代碼編制真正工程化,設(shè)計(jì)模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。q GoF的“設(shè)計(jì)模式”是第一次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化, GoF提出了23種基本設(shè)計(jì)模式,自此,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過(guò)程中,新的大量的設(shè)計(jì)模式不斷出現(xiàn)。3GoFRalph JohnsonRichard HelmErich Gamma
2、John Vlissides4設(shè)計(jì)模式q 一、設(shè)計(jì)模式和框架一、設(shè)計(jì)模式和框架 q 現(xiàn)在,可復(fù)用面向?qū)ο筌浖到y(tǒng)現(xiàn)在一般劃分為三大類:應(yīng)用程序工具箱和框架(Framework),我們平時(shí)開(kāi)發(fā)的具體軟件都是應(yīng)用程序;Java的API屬于工具箱;而框架是構(gòu)成一類特定軟件可復(fù)用設(shè)計(jì)的一組相互協(xié)作的類。EJB(EnterpriseJavaBeans)是Java應(yīng)用于企業(yè)計(jì)算的框架.q 框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)類和對(duì)象的關(guān)系等等設(shè)計(jì)參數(shù),以便于具體應(yīng)用實(shí)現(xiàn)者能集中精力于應(yīng)用本身的特定細(xì)節(jié)。框架主要記錄軟件應(yīng)用中共同的設(shè)計(jì)決策,框架強(qiáng)調(diào)設(shè)計(jì)復(fù)用,因此框架設(shè)計(jì)中必然要使用設(shè)計(jì)模式.5設(shè)計(jì)模式q另外,
3、設(shè)計(jì)模式有助于對(duì)框架結(jié)構(gòu)的理解,成熟的框架通常使用了多種設(shè)計(jì)模式,如果你熟悉這些設(shè)計(jì)模式,毫無(wú)疑問(wèn),你將迅速掌握框架的結(jié)構(gòu),我們一般開(kāi)發(fā)者如果突然接觸EJB, J2EE等框架,會(huì)覺(jué)得特別難學(xué),難掌握,那么轉(zhuǎn)而先掌握設(shè)計(jì)模式,無(wú)疑是給了你剖析EJB或J2EE系統(tǒng)的一把利器。6設(shè)計(jì)模式q二、設(shè)計(jì)模式的原則二、設(shè)計(jì)模式的原則 q近年來(lái),大家都開(kāi)始注意設(shè)計(jì)模式。那么,到底我們?yōu)槭裁匆迷O(shè)計(jì)模式呢?這么多設(shè)計(jì)模式為什么要這么設(shè)計(jì)呢?q為什么要提倡“Design Pattern”呢?根本原因是為了代碼復(fù)用,增加可維護(hù)性。那么怎么才能實(shí)現(xiàn)代碼復(fù)用呢?OO界有前輩的幾個(gè)原則:“開(kāi)閉”原則(Open Close
4、d Principal)、里氏代換原則、合成復(fù)用等原則。設(shè)計(jì)模式就是實(shí)現(xiàn)了這些原則,從而達(dá)到了代碼復(fù)用、增加可維護(hù)性的目的。 7設(shè)計(jì)模式q1、開(kāi)閉原則 q此原則是由“Bertrand Meyer”提出的。原文是:“Software entities should be open for extension,but closed for modification”。就是說(shuō)模塊應(yīng)對(duì)擴(kuò)展開(kāi)放,而對(duì)修改關(guān)閉。模塊應(yīng)盡量在不修改原(是“原”,指原來(lái)的代碼)代碼的情況下進(jìn)行擴(kuò)展。那么怎么擴(kuò)展呢?q我們看工廠模式factory pattern:8設(shè)計(jì)模式q2、里氏代換原則 q里氏代換原則是由“Barbara
5、 Liskov”提出的 q基類出現(xiàn)的地方,子類一定可以出現(xiàn)q如果調(diào)用的是父類的話,那么換成子類也完全可以運(yùn)行 9設(shè)計(jì)模式q3、聚合/組合復(fù)用原則 q盡量使用聚合/組合,而不是繼承達(dá)到復(fù)用q就是說(shuō)要少用繼承,多用聚合/組合關(guān)系來(lái)實(shí)現(xiàn) q在Java中,應(yīng)盡量針對(duì)Interface編程,而非實(shí)現(xiàn)類。這樣,更換子類不會(huì)影響調(diào)用它方法的代碼。要讓各個(gè)類盡可能少的跟別的類聯(lián)系,不要與陌生人說(shuō)話。這樣,城門(mén)失火,才不至于殃及池魚(yú)。擴(kuò)展性和維護(hù)性才能提高10設(shè)計(jì)模式q4 、依賴倒轉(zhuǎn)原則 q抽象不應(yīng)該依賴與細(xì)節(jié),細(xì)節(jié)應(yīng)當(dāng)依賴與抽象。q要針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。q傳遞參數(shù),或者在組合聚合關(guān)系中,盡量引用
6、層次高的類。q主要是在構(gòu)造對(duì)象時(shí)可以動(dòng)態(tài)的創(chuàng)建各種具體對(duì)象,當(dāng)然如果一些具體類比較穩(wěn)定,就不必在弄一個(gè)抽象類做它的父類,這樣有畫(huà)蛇添足的感覺(jué)11設(shè)計(jì)模式q5、單一職責(zé)原則(SRP)q就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。12設(shè)計(jì)模式q 6 、接口隔離原則q為客戶端提供盡可能小的單獨(dú)的接口q定制服務(wù)的接口,每一個(gè)接口應(yīng)該是一種角色,不多不少,不干不該干的事,該干的事都要干13設(shè)計(jì)模式q7、 迪米特法則(LoD)q一個(gè)軟件實(shí)體與盡可能少的實(shí)體相互作用q也叫最少知識(shí)原則。q不要和陌生人說(shuō)話。q如果兩個(gè)類不必彼此直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用。如果其中一個(gè)類需要調(diào)用另一個(gè)類的某
7、個(gè)方法的話,可以通過(guò)第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。14設(shè)計(jì)模式q理解了這些原則,再看設(shè)計(jì)模式,只是在具體問(wèn)題上怎么實(shí)現(xiàn)這些原則而已。張無(wú)忌學(xué)太極拳,忘記了所有招式,打倒了玄冪二老,所謂心中無(wú)招。設(shè)計(jì)模式可謂招數(shù),如果先學(xué)通了各種模式,又忘掉了所有模式而隨心所欲,可謂OO之最高境界。15一、創(chuàng)建型模式q Abstract Factory:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無(wú)需指定它們具體的類。q Builder:將一個(gè)復(fù)雜對(duì)象的構(gòu)件與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表述。q Factory Method:定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定將哪一個(gè)類實(shí)例化。Factory Me
8、thod使一個(gè)類的實(shí)例化延遲到其子類。16一、創(chuàng)建型模式qPrototype:用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這個(gè)原型來(lái)創(chuàng)建新的對(duì)象。q Singleton:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。17二、結(jié)構(gòu)型模式q Adapter:將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。q Bridge:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。q Composite:將對(duì)象組合成樹(shù)型結(jié)構(gòu)以表示“部分整體”的層次結(jié)構(gòu)。Composite使得客戶對(duì)單個(gè)對(duì)象和復(fù)合對(duì)象的使用具有一致性。18二、結(jié)
9、構(gòu)型模式q Decorator:動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就擴(kuò)展功能而言,Decorator模式比生成子類方式更為靈活。q Facade:為子系統(tǒng)中的一組接口提供一個(gè)一致的界面,F(xiàn)acade模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。q Flyweight:運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度的對(duì)象。q Proxy:為其他對(duì)象提供一個(gè)代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。19三、行為型模式q Chain of Responsibility:為解除請(qǐng)求的發(fā)送者和接受者之間耦合,而使多個(gè)對(duì)象都有機(jī)會(huì)處理這個(gè)請(qǐng)求。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它。q C
10、ommand:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可以用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可取消的操作。q Interpreter:給定一個(gè)語(yǔ)言,定義它的文法的一種表示,并定義一個(gè)解釋器,該解釋器使用該表示來(lái)解釋語(yǔ)言中的句子。20三、行為型模式q Iterator:提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中各個(gè)元素,而又不需暴露該對(duì)象的內(nèi)部表示。q Mediator:用一個(gè)中介對(duì)象來(lái)封裝一系列的對(duì)象交互。中介者使各對(duì)象不需要顯式地相互引用,從而使器耦合松散,而且可以獨(dú)立地改變它們之間的交互。q Memento:在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存
11、這個(gè)狀態(tài)。這樣以后就可以將該對(duì)象恢復(fù)到保存的狀態(tài)。21三、行為型模式qObserver:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,以便當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并自動(dòng)刷新。q State:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。對(duì)象看起來(lái)似乎修改了它所屬的類。q Strategy:定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái),并且使它們可相互替換。本模式使得算法的變化可獨(dú)立于使用它的客戶。22qTemplate Method:定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。q
12、 Visitor:表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。23設(shè)計(jì)模式之單例模式 q作為對(duì)象的創(chuàng)建模式GOF95, 單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為單例類。q單例模式的要點(diǎn)單例模式的要點(diǎn)單例模式的要點(diǎn)有三個(gè);q一是某各類只能有一個(gè)實(shí)例;q二是它必須自行創(chuàng)建這個(gè)實(shí)例;q三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。 24單例模式q 在上面的對(duì)象圖中,有一個(gè)單例對(duì)象,而客戶甲、客戶乙 和客戶丙是單例對(duì)象的三個(gè)客戶對(duì)象??梢钥吹剑械目蛻魧?duì)象共享一個(gè)單例對(duì)象。而且從單例對(duì)象到自身的連接線可
13、以看出,單例對(duì)象持有對(duì)自己的引用。25單例模式q 一些資源管理器常常設(shè)計(jì)設(shè)計(jì)成單例模式。 q 在計(jì)算機(jī)系統(tǒng)中,需要管理的資源包括軟件外部資源,譬如每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler, 以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干傳真卡,但是只應(yīng)該有一個(gè)軟件負(fù)責(zé)管理傳真卡,以避免出現(xiàn)兩份傳真作業(yè)同時(shí)傳到傳真卡中的情況。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用。需要管理的資源包括軟件內(nèi)部資源,譬如,大多數(shù)的軟件都有一個(gè)(甚至多個(gè))屬性(properties)文件存放系統(tǒng)配置。這樣的系統(tǒng)應(yīng)
14、當(dāng)由一個(gè)對(duì)象來(lái)管理一個(gè)屬性文件。 26單例模式q需要管理的軟件內(nèi)部資源也包括譬如負(fù)責(zé)記錄網(wǎng)站來(lái)訪人數(shù)的部件,記錄軟件系統(tǒng)內(nèi)部事件、出錯(cuò)信息的部件,或是對(duì)系統(tǒng)的表現(xiàn)進(jìn)行檢查的部件等。這些部件都必須集中管理,不可政出多頭。這些資源管理器構(gòu)件必須只有一個(gè)實(shí)例,這是其一;它們必須自行初始化,這是其二;允許整個(gè)系統(tǒng)訪問(wèn)自己這是其三。因此,它們都滿足單例模式的條件,是單例模式的應(yīng)用。 27單例模式q一個(gè)例子:一個(gè)例子:Windows 回收站回收站q在整個(gè)視窗系統(tǒng)中,回收站只能有一個(gè)實(shí)例,整個(gè)系統(tǒng)都使用這個(gè)惟一的實(shí)例,而且回收站自行提供自己的實(shí)例。因此,回收站是單例模式的應(yīng)用。28單例模式q單例模式的結(jié)構(gòu)單
15、例模式的結(jié)構(gòu)雖然單例模式中的單例類被限定只能有一個(gè)實(shí)例,但是單例模式和單例類可以很容易被推廣到任意且有限多個(gè)實(shí)例的情況,這時(shí)候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class),單例類的簡(jiǎn)略類圖如下所示。 29q由于Java 語(yǔ)言的特點(diǎn),使得單例模式在Java 語(yǔ)言的實(shí)現(xiàn)上有自己的特點(diǎn)。這些特點(diǎn)主要表現(xiàn)在單例類如何將自己實(shí)例化上。q餓漢式單例類餓漢式單例類是在Java 語(yǔ)言里實(shí)現(xiàn)得最為簡(jiǎn)便的單例類,下面所示的類圖描述了一個(gè)餓漢式單例類的典型實(shí)現(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可以看出,在這個(gè)類被加載時(shí),靜態(tài)變量m_instance 會(huì)被初始化,此時(shí)類的私有構(gòu)造子會(huì)被調(diào)用。這時(shí)候,單例類的惟一實(shí)例就被創(chuàng)建出來(lái)了。Java 語(yǔ)言中單例類的一個(gè)最重要的特點(diǎn)是類的構(gòu)造子是私有的,從而避免外界利用構(gòu)造子直接創(chuàng)建出任意多的實(shí)例。值得指出的是
17、,由于構(gòu)造子是私有的,因此,此類不能被繼承。32q懶漢式單例類懶漢式單例類 q與餓漢式單例類相同之處是,類的構(gòu)造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時(shí)將自己實(shí)例化。如果加載器是靜態(tài)的,那么在懶漢式單例類被加載時(shí)不會(huì)將自己實(shí)例化。如下圖所示,類圖中給出了一個(gè)典型的餓漢式單例類實(shí)現(xiàn)。33qpackage com.javapatterns.singleton.demos; public class LazySingleton private static LazySingleton m_instance = null; /* * 私有的默認(rèn)構(gòu)造子,保證外界無(wú)法直接實(shí)例化 *
18、/ private LazySingleton() /* * 靜態(tài)工廠方法,返還此類的惟一實(shí)例 */ synchronized public static LazySingleton getInstance() if (m_instance = null) m_instance = new LazySingleton(); return m_instance; 34q 讀者可能會(huì)注意到,在上面給出懶漢式單例類實(shí)現(xiàn)里對(duì)靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。有些設(shè)計(jì)設(shè)計(jì)師在這里建議使用所謂的雙重檢查成例。必須指出的是,雙重檢查成例不可以在Java 語(yǔ)言中使用。不十分熟悉的讀者,可以看看后面給
19、出的小節(jié)。同樣,由于構(gòu)造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時(shí)就將自己實(shí)例化。即便加載器是靜態(tài)的,在餓漢式單例類被加載時(shí)仍會(huì)將自己實(shí)例化。單從資源利用效率角度來(lái)講,這個(gè)比懶漢式單例類稍差些。35q 從速度和反應(yīng)時(shí)間角度來(lái)講,則比懶漢式單例類稍好些。然而,懶漢式單例類在實(shí)例化時(shí), 必須處理好在多個(gè)線程同時(shí)首次引用此類時(shí)的訪問(wèn)限制問(wèn)題,特別是當(dāng)單例類作為資源控制器,在實(shí)例化時(shí)必然涉及資源初始化,而資源初始化很有可能耗費(fèi)時(shí)間。這意味著出現(xiàn)多線程同時(shí)首次引用此類的機(jī)率變得較大。餓漢式單例類可以在Java 語(yǔ)言內(nèi)實(shí)現(xiàn), 但不易在C+ 內(nèi)實(shí)現(xiàn),因?yàn)殪o態(tài)初始化在C+ 里沒(méi)有固定的順序,
20、因而靜態(tài)的m_instance 變量的初始化與類的加載順序沒(méi)有保證,可能會(huì)出問(wèn)題。這就是為什么GoF 在提出單例類的概念時(shí),舉的例子是懶漢式的。他們的書(shū)影響之大,以致Java 語(yǔ)言中單例類的例子也大多是懶漢式的。實(shí)際上,本書(shū)認(rèn)為餓漢式單例類更符合Java 語(yǔ)言本身的特點(diǎn)。 36q登記式單例類登記式單例類登記式單例類是GoF 為了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點(diǎn)而設(shè)計(jì)設(shè)計(jì)的。本書(shū)把他們的例子翻譯為Java 語(yǔ)言,并將它自己實(shí)例化的方式從懶漢式改為餓漢式。只是它的子類實(shí)例化的方式只能是懶漢式的, 這是無(wú)法改變的。如下圖所示是登記式單例類的一個(gè)例子,圖中的關(guān)系線表明,此類已將自己實(shí)例化
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); /* * 保護(hù)的默認(rèn)構(gòu)造子 */ protected RegSingleton() /* * 靜態(tài)工廠方法,返還此類惟一的實(shí)例 */ 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) ); /* * 一個(gè)示意
23、性的商業(yè)方法 */ public String about() return Hello, I am RegSingleton.; 39q它的子類RegSingletonChild 需要父類的幫助才能實(shí)例化。下圖所示是登記式單例類子類的一個(gè)例子。圖中的關(guān)系表明,此類是由父類將子類實(shí)例化的。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 ); /* * 一個(gè)示意性的商業(yè)方法 */ public String about() return Hello, I am RegSingletonChild.; 41q 在GoF 原始的例子中,并沒(méi)有g(shù)etInstance() 方法,這樣得到子類必須調(diào)用的getInstance(String name)方法并傳入子類的名字,因此很不方便。本章在登記式單例類
25、子類的例子里,加入了getInstance() 方法,這樣做的好處是RegSingletonChild 可以通過(guò)這個(gè)方法,返還自已的實(shí)例。而這樣做的缺點(diǎn)是,由于數(shù)據(jù)類型不同,無(wú)法在RegSingleton 提供這樣一個(gè)方法。由于子類必須允許父類以構(gòu)造子調(diào)用產(chǎn)生實(shí)例,因此,它的構(gòu)造子必須是公開(kāi)的。這樣一來(lái),就等于允許了以這樣方式產(chǎn)生實(shí)例而不在父類的登記中。這是登記式單例類的一個(gè)缺點(diǎn)。GoF 曾指出,由于父類的實(shí)例必須存在才可能有子類的實(shí)例,這在有些情況下是一個(gè)浪費(fèi)。這是登記式單例類的另一個(gè)缺點(diǎn)。42q在什么情況下使用單例模式在什么情況下使用單例模式使用單例模式有一個(gè)很重要的必要條件:在一個(gè)系統(tǒng)要
26、求一個(gè)類只有一個(gè)實(shí)例時(shí)才應(yīng)當(dāng)使用單例模式。反過(guò)來(lái)說(shuō),如果一個(gè)類可以有幾個(gè)實(shí)例共存,那么就沒(méi)有必要使用單例類。但是有經(jīng)驗(yàn)的讀者可能會(huì)看到很多不當(dāng)?shù)厥褂脝卫J降睦樱梢?jiàn)做到上面這一點(diǎn)并不容易,下面就是一些這樣的情況。43q例子一 問(wèn):我的一個(gè)系統(tǒng)需要一些“全程”變量。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類盛放所有的“全程”變量。請(qǐng)問(wèn)這樣做對(duì)嗎?答:這樣做是違背單例模式的用意的。單例模式只應(yīng)當(dāng)在有真正的單一實(shí)例的需求時(shí)才可使用。一個(gè)設(shè)計(jì)設(shè)計(jì)得當(dāng)?shù)南到y(tǒng)不應(yīng)當(dāng)有所謂的全程變量,這些變量應(yīng)當(dāng)放到它們所描述的實(shí)體所對(duì)應(yīng)的類中去。將這些變量從它們所描述的實(shí)體類中抽出來(lái), 放到一個(gè)不相干的單例類中去,會(huì)
27、使得這些變量產(chǎn)生錯(cuò)誤的依賴關(guān)系和耦合關(guān)系。 44q例子二問(wèn):我的一個(gè)系統(tǒng)需要管理與數(shù)據(jù)庫(kù)的連接。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類包裝一個(gè)Connection 對(duì)象,并在finalize()方法中關(guān)閉這個(gè)Connection 對(duì)象。這樣的話,在這個(gè)單例類的實(shí)例沒(méi)有被人引用時(shí),這個(gè)finalize() 對(duì)象就會(huì)被調(diào)用,因此,Connection 對(duì)象就會(huì)被釋放。這多妙啊。45q答:這樣做是不恰當(dāng)?shù)?。除非有單一?shí)例的需求,不然不要使用單例模式。在這里Connection 對(duì)象可以同時(shí)有幾個(gè)實(shí)例共存,不需要是單一實(shí)例。單例模式有很多的錯(cuò)誤使用案例都與此例子相似,它們都是試圖使用單例模式管理共
28、享資源的生命周期,這是不恰當(dāng)?shù)摹?46q單例類的狀態(tài)單例類的狀態(tài) q有狀態(tài)的單例類有狀態(tài)的單例類一個(gè)單例類可以是有狀態(tài)的(stateful),一個(gè)有狀態(tài)的單例對(duì)象一般也是可變(mutable) 單例對(duì)象。有狀態(tài)的可變的單例對(duì)象常常當(dāng)做狀態(tài)庫(kù)(repositary)使用。比如一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來(lái)給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣(mài)系統(tǒng)的賬單號(hào)碼。當(dāng)然,一個(gè)單例類可以持有一個(gè)聚集,從而允許存儲(chǔ)多個(gè)狀態(tài)。 47q沒(méi)有狀態(tài)的單例類沒(méi)有狀態(tài)的單例類另一方面,單例類也可以是沒(méi)有狀態(tài)的(stateless), 僅用做提供工具性函數(shù)的對(duì)象。既然是為了提供工具性函數(shù),也
29、就沒(méi)有必要?jiǎng)?chuàng)建多個(gè)實(shí)例,因此使用單例模式很合適。一個(gè)沒(méi)有狀態(tài)的單例類也就是不變(Immutable) 單例類; 關(guān)于不變模式,讀者可以參見(jiàn)本書(shū)的不變(Immutable )模式一章。48q多個(gè)多個(gè)JVM 系統(tǒng)的分散式系統(tǒng)系統(tǒng)的分散式系統(tǒng)EJB 容器有能力將一個(gè)EJB 的實(shí)例跨過(guò)幾個(gè)JVM 調(diào)用。由于單例對(duì)象不是EJB,因此,單例類局限于某一個(gè)JVM 中。換言之,如果EJB 在跨過(guò)JVM 后仍然需要引用同一個(gè)單例類的話,這個(gè)單例類就會(huì)在數(shù)個(gè)JVM 中被實(shí)例化,造成多個(gè)單例對(duì)象的實(shí)例出現(xiàn)。一個(gè)J2EE應(yīng)用系統(tǒng)可能分布在數(shù)個(gè)JVM 中,這時(shí)候不一定需要EJB 就能造成多個(gè)單例類的實(shí)例出現(xiàn)在不同JVM
30、 中的情況。 49q如果這個(gè)單例類是沒(méi)有狀態(tài)的,那么就沒(méi)有問(wèn)題。因?yàn)闆](méi)有狀態(tài)的對(duì)象是沒(méi)有區(qū)別的。但是如果這個(gè)單例類是有狀態(tài)的, 那么問(wèn)題就來(lái)了。舉例來(lái)說(shuō),如果一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來(lái)給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣(mài)系統(tǒng)的賬單號(hào)碼的話,用戶會(huì)看到同一個(gè)號(hào)碼出現(xiàn)好幾次。在任何使用了EJB、RMI 和JINI 技術(shù)的分散式系統(tǒng)中,應(yīng)當(dāng)避免使用有狀態(tài)的單例模式。50q 多個(gè)類加載器多個(gè)類加載器 q 同一個(gè)JVM 中會(huì)有多個(gè)類加載器,當(dāng)兩個(gè)類加載器同時(shí)加載同一個(gè)類時(shí),會(huì)出現(xiàn)兩個(gè)實(shí)例。在很多J2EE 服務(wù)器允許同一個(gè)服務(wù)器內(nèi)有幾個(gè)Servlet 引擎時(shí),每一個(gè)引擎
31、都有獨(dú)立的類加載器,經(jīng)有不同的類加載器加載的對(duì)象之間是絕緣的。比如一個(gè)J2EE 系統(tǒng)所在的J2EE 服務(wù)器中有兩個(gè)Servlet 引擎:一個(gè)作為內(nèi)網(wǎng)給公司的網(wǎng)站管理人員使用;另一個(gè)給公司的外部客戶使用。兩者共享同一個(gè)數(shù)據(jù)庫(kù),兩個(gè)系統(tǒng)都需要調(diào)用同一個(gè)單例類。如果這個(gè)單例類是有狀態(tài)的單例類的話,那么內(nèi)網(wǎng)和外網(wǎng)用戶看到的單例對(duì)象的狀態(tài)就會(huì)不同。除非系統(tǒng)有協(xié)調(diào)機(jī)制,不然在這種情況下應(yīng)當(dāng)盡量避免使用有狀態(tài)的單例類。 51q一個(gè)實(shí)用的例子:屬性管理器一個(gè)實(shí)用的例子:屬性管理器這里給出一個(gè)讀取屬性(properties) 文件的單例類,作為單例模式的一個(gè)實(shí)用的例子。屬性文件如同老式的視窗編程時(shí)的.ini 文
32、件,用于存放系統(tǒng)的配置信息。配置信息在屬性文件中以屬性的方式存放,一個(gè)屬性就是兩個(gè)字符串組成的對(duì)子,其中一個(gè)字符串是鍵(key),另一個(gè)字符串是這個(gè)鍵的值(value)。52q大多數(shù)的系統(tǒng)都有一些配置常量,這些常量如果是存儲(chǔ)在程序程序內(nèi)部的,那么每一次修改這些常量都需要重新編譯程序。將這些常量放在配置文件中,系統(tǒng)通過(guò)訪問(wèn)這個(gè)配置文件取得配置常量,就可以通過(guò)修改配置文件而無(wú)需修改程序而達(dá)到更改系統(tǒng)配置的目的。系統(tǒng)也可以在配置文件中存儲(chǔ)一些工作環(huán)境信息,這樣在系統(tǒng)重啟時(shí),這些工作信息可以延續(xù)到下一個(gè)運(yùn)行周期中。假定需要讀取的屬性文件就在當(dāng)前目錄中,且文件名為perties
33、 。這個(gè)文件中有如下的一些屬性項(xiàng)。 53q屬性文件內(nèi)容node1.item1=How node1.item2=are node2.item1=you node2.item2=doing node3.item1=?q例如,node1.item1 就是一個(gè)鍵,而How 就是這個(gè)鍵所對(duì)應(yīng)的值。54qJava 屬性類屬性類Java 提供了一個(gè)工具類,稱做屬性類,可以用來(lái)完成Java 屬性和屬性文件的操作。這個(gè)屬性類的繼承關(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)鍵字個(gè)數(shù)。設(shè)置屬性的方法有:. put(Object key, Object value) :向?qū)傩员碇凶芳訉傩躁P(guān)鍵字和關(guān)鍵字的值。. remove(Obj
35、ect key):從屬性表中刪除關(guān)鍵字。57q從屬性文件加載屬性的方法為load(InputStream inStream),可以從一個(gè)輸入流中讀入一個(gè)屬性列,如果這個(gè)流是來(lái)自一個(gè)文件的話,這個(gè)方法就從文件中讀入屬性。將屬性存入屬性文件的方法有幾個(gè),重要的一個(gè)是store(OutputStream out, String header) ,將當(dāng)前的屬性列寫(xiě)入一個(gè)輸出流,如果這個(gè)輸出流是導(dǎo)向一個(gè)文件的,那么這個(gè)方法就將屬性流存入文件。58q為什么需要使用單例模式為什么需要使用單例模式屬性是系統(tǒng)的一種資源,應(yīng)當(dāng)避免有多余一個(gè)的對(duì)象讀取特別是存儲(chǔ)屬性。此外,屬性的讀取可能會(huì)在很多地方發(fā)生,創(chuàng)建屬性對(duì)
36、象的地方應(yīng)當(dāng)在哪里不是很清楚。換言之,屬性管理器應(yīng)當(dāng)自己創(chuàng)建自己的實(shí)例,并且自己向系統(tǒng)全程提供這一事例。因此,屬性文件管理器應(yīng)當(dāng)是一個(gè)單例模式負(fù)責(zé)。59q系統(tǒng)系統(tǒng)設(shè)計(jì)設(shè)計(jì)系統(tǒng)的核心是一個(gè)屬性管理器,也就是一個(gè)叫做ConfigManager 的類,這個(gè)類應(yīng)當(dāng)是一個(gè)單例類。因此,這個(gè)類應(yīng)當(dāng)有一個(gè)靜態(tài)工廠方法,不妨叫做getInstance(), 用于提供自己的實(shí)例。為簡(jiǎn)單起見(jiàn),本文在這里采取餓漢方式實(shí)現(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; /* * 對(duì)應(yīng)于屬性文件的文件對(duì)象變量 */ private File m_file = null; 62q /* * 屬性文件的最后修改日期 */ private long m_lastModifiedTime = 0; /* * 屬性文件所對(duì)應(yīng)的屬性對(duì)象變量 */ pri
38、vate Properties m_props = null; /* * 本類可能存在的惟一的一個(gè)實(shí)例 */ private static ConfigManager m_instance = new ConfigManager(); 63q /* * 私有的構(gòu)造子,用以保證外界無(wú)法直接實(shí)例化 */ 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 類的單一實(shí)例 */ synchronized public static ConfigManager getInstance() return m_instance; /* * 讀取一特定的屬性項(xiàng) * * param name 屬性項(xiàng)的項(xiàng)名
40、* param defaultVal 屬性項(xiàng)的默認(rèn)值 * return 屬性項(xiàng)的值(如此項(xiàng)存在), 默認(rèn)值(如此項(xiàng)不存在) */ 66qfinal public Object getConfigItem( String name, Object defaultVal) long newTime = m_file.lastModified(); / 檢查屬性文件是否被其他程序程序 / (多數(shù)情況是程序員手動(dòng))修改過(guò) / 如果是,重新讀取此文件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在上面直接使用了一個(gè)局域的常量?jī)?chǔ)存儲(chǔ)屬性文件的路徑。在實(shí)際的系統(tǒng)中,讀者可以采取更靈活的方式將屬性文件的路徑傳入。讀者可以看到,這個(gè)管理器類有一個(gè)很有意思的功能,即在每一次調(diào)用時(shí),檢查屬性文件是否已經(jīng)被更新過(guò)。如果確實(shí)已經(jīng)被更新過(guò)的話,管理器會(huì)自動(dòng)重新加載屬性文件, 從而保證管理器的內(nèi)容與屬性文件的內(nèi)容總是一致的。69q怎樣調(diào)用屬性管理器怎樣調(diào)用屬性管理器下面的源代碼演示了怎樣調(diào)用ConfigManager
43、 來(lái)讀取屬性文件。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 語(yǔ)言中的單例模式語(yǔ)言中的單例模式Java 語(yǔ)言中就有很多的單例模式的應(yīng)用實(shí)例,這里討論比較有名的幾個(gè)。Java 的的Runtime 對(duì)象對(duì)象在Java 語(yǔ)言內(nèi)部,java.lang.Runtime 對(duì)象就是一個(gè)使用單例模式的例子。在每一個(gè)Java 應(yīng)用程序程序里面,都有惟一的一個(gè)Runtime 對(duì)象。通過(guò)這個(gè)Runtime 對(duì)象,應(yīng)用程序可以與其運(yùn)行環(huán)境發(fā)生相互作用。Runtime 類提供一個(gè)靜態(tài)工廠方法getRuntime():: q public static Runtime getRuntime();72q
45、通過(guò)調(diào)用此方法,可以獲得Runtime 類惟一的一個(gè)實(shí)例: Runtime rt = Runtime getRuntime(); Runtime 對(duì)象通常的用途包括:執(zhí)行外部命令;返回現(xiàn)有內(nèi)存即全部?jī)?nèi)存;運(yùn)行垃圾收集器;加載動(dòng)態(tài)庫(kù)等。下面的例子演示了怎樣使用Runtime 對(duì)象運(yùn)行一個(gè)外部程序。代碼清單8:怎樣使用Runtime 對(duì)象運(yùn)行一個(gè)外部命令73qimport java.io.*; public class CmdTest public static void main(String args) throws IOException Process proc = Runtime.get
46、Runtime().exec(notepad.exe); 74q 上面的程序在運(yùn)行時(shí)會(huì)打開(kāi)notepad 程序。應(yīng)當(dāng)指出的是,在Windows 2000 的環(huán)境中,如果需要打開(kāi)一個(gè)Word 文件,而又不想指明Word 軟件軟件安裝的位置時(shí),可以使用下面的做法: q Process proc = Runtime.getRuntime().exec( cmd /E:ON /c start MyDocument.doc); q 在上面,被執(zhí)行的命令是start MyDocument.doc ,開(kāi)關(guān)E:ON 指定DOS 命令處理器允許命令擴(kuò)展,而開(kāi)關(guān)/C 指明后面跟隨的字符串是命令,并在執(zhí)行命令后關(guān)閉
47、DOS 窗口,start 命令會(huì)開(kāi)啟一個(gè)單獨(dú)的窗口執(zhí)行所提供的命令。75qIntrospector 類類一般的應(yīng)用程序可能永遠(yuǎn)也不會(huì)直接用到Introspector 類,但讀者應(yīng)該知道Introspector 是做什么的。Sun 提供了一個(gè)叫做BeanBox 的系統(tǒng),允許動(dòng)態(tài)地加載JavaBean ,并動(dòng)態(tài)地修改其性質(zhì)。BeanBox 在運(yùn)行時(shí)的情況如下圖所示。7677q 在上面的圖中顯示了BeanBox 最重要的兩個(gè)視窗,一個(gè)叫做BeanBox 視窗,另一個(gè)叫做性質(zhì)視窗。在上面的BeanBox 視窗中顯示了一個(gè)Juggler Bean 被放置到視窗中的情況。相應(yīng)的,在性質(zhì)視窗中顯示了Jugg
48、ler Bean 的所有性質(zhì)。所有的Java 集成環(huán)境都提供這種功能,這樣的系統(tǒng)就叫做BeanBox 系統(tǒng)。BeanBox 系統(tǒng)使用一種自?。↖ntrospection )過(guò)程來(lái)確定一個(gè)Bean 所輸出的性質(zhì)、事件和方法。這個(gè)自省機(jī)制是通過(guò)自省者類,也即java.util.Introspector 類實(shí)現(xiàn)的;這個(gè)機(jī)制是建立在Java 反射(Reflection) 機(jī)制和命名規(guī)范的基礎(chǔ)之上的。比如,Introspector 類可以確定Juggler Bean 所支持的所有的性質(zhì),這是因?yàn)镮ntrospector 類可以得到所有的方法,然后將其中的取值和賦值方法以及它們的特征加以比較,從而得出結(jié)果
49、。顯然,在整個(gè)BeanBox 系統(tǒng)中只需要一個(gè)Introspector 對(duì)象,下面所示就是這個(gè)類的結(jié)構(gòu)圖。78q 可以看出,Introspector 類的構(gòu)造子是私有的, 一個(gè)靜態(tài)工廠方法instantiate() 提供了Instrospector 類的惟一實(shí)例。換言之,這個(gè)類是單例模式的應(yīng)用。q java.awt.Toolkit 類類Toolkit 類是一個(gè)非常有趣的單例模式的例子。Toolkit 使用單例模式創(chuàng)建所謂的Toolkit 的默認(rèn)對(duì)象,并且確保這個(gè)默認(rèn)實(shí)例在整個(gè)系統(tǒng)中是惟一的。Toolkit 類提供了一個(gè)靜態(tài)的方法getDefaultToolkit() 來(lái)提供這個(gè)惟一的實(shí)例,這個(gè)
50、方法相當(dāng)于懶漢式的單例方法,因此整個(gè)方法都是同步化的。79q代碼清單9:getDefaultToolkit() 方法 public static synchronized Toolkit getDefaultToolkit() . 80q其中性質(zhì)defaultToolkit 實(shí)際上就是靜態(tài)的getDefaultToolkit 類。有趣的是,由于Toolkit 是一個(gè)抽象類,因此其子類如果提供一個(gè)私有的構(gòu)造子,那么其子類便是一個(gè)正常的單例類;而如果其子類作為具體實(shí)現(xiàn)提供一個(gè)公開(kāi)的構(gòu)造子, 這時(shí)候這個(gè)具體子類便是 不完全的單例類。關(guān)于不完全的單例類的討論請(qǐng)見(jiàn)本章后面的專題:不完全的單例類一節(jié) 81
51、q 模版方法模式模版方法模式同時(shí),熟悉模版方法模式的讀者可以看出,getDefaultToolkit() 方法實(shí)際上是一個(gè)模版方法。私有構(gòu)造子是推遲到子類實(shí)現(xiàn)的剩余邏輯,根據(jù)子類對(duì)這個(gè)剩余邏輯的不同實(shí)現(xiàn), 子類就可以提供完全不同的行為。對(duì)Toolkit 的子類而言,私有構(gòu)造子依賴于操作系統(tǒng)操作系統(tǒng),不同的子類可以根據(jù)不同的操作系統(tǒng)而給出不同的邏輯,從而使Toolkit 的子類對(duì)不同的操作系統(tǒng)給出不同的行為。javax.swing.TimerQueue 類類這是一個(gè)不完全的單例類,由于這個(gè)類是在Swing 的定時(shí)器類中使用的,因此我們將在后面介紹。 82q不完全的單例類不完全的單例類 什么是不完全的單例類估計(jì)有些讀者見(jiàn)過(guò)下面這樣的“不完全”的單例類。83qpackage com.javapatterns.singleton.demos; public class LazySingleton private static LazySinglet
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 貴州城市職業(yè)學(xué)院《中國(guó)文化》2023-2024學(xué)年第一學(xué)期期末試卷
- 2025年廣東建筑安全員-B證(項(xiàng)目經(jīng)理)考試題庫(kù)
- 2025山西省建筑安全員B證(項(xiàng)目經(jīng)理)考試題庫(kù)
- 貴陽(yáng)信息科技學(xué)院《GS原理與技術(shù)》2023-2024學(xué)年第一學(xué)期期末試卷
- 廣州珠江職業(yè)技術(shù)學(xué)院《藥物分子生物學(xué)》2023-2024學(xué)年第一學(xué)期期末試卷
- 2025山東省建筑安全員C證考試(專職安全員)題庫(kù)及答案
- 2025年云南建筑安全員A證考試題庫(kù)
- 2025年山東省建筑安全員-B證考試題庫(kù)附答案
- 2025黑龍江省建筑安全員A證考試題庫(kù)及答案
- 2025福建建筑安全員A證考試題庫(kù)
- 中職班主任德育培訓(xùn)
- 中科院簡(jiǎn)介介紹
- 《小石潭記》教學(xué)實(shí)錄及反思特級(jí)教師-王君
- 【高中語(yǔ)文】《錦瑟》《書(shū)憤》課件+++統(tǒng)編版+高中語(yǔ)文選擇性必修中冊(cè)+
- 醫(yī)療機(jī)構(gòu)(醫(yī)院)停電和突然停電應(yīng)急預(yù)案試題及答案
- 24年海南生物會(huì)考試卷
- 國(guó)家戰(zhàn)略思維課件
- 施工單位自評(píng)報(bào)告
- 招商租金政策方案
- 銀行金庫(kù)集中可行性報(bào)告
- 工程結(jié)算中的風(fēng)險(xiǎn)識(shí)別與防控
評(píng)論
0/150
提交評(píng)論