每日一得

          不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發(fā)
          最近關(guān)心的內(nèi)容:SSH,seam,flex,敏捷,TDD
          本站的官方站點(diǎn)是:顛覆軟件

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評(píng)論 :: 0 Trackbacks

          一、 引子

          單例模式是設(shè)計(jì)模式中使用很頻繁的一種模式,在各種開源框架、應(yīng)用系統(tǒng)中多有應(yīng)用,在我前面的幾篇文章中也結(jié)合其它模式使用到了單例模式。這里我們就單例模式進(jìn)行系統(tǒng)的學(xué)習(xí)。并對(duì)有人提出的 單例模式是邪惡的 這個(gè)觀點(diǎn)進(jìn)行了一定的分析。

          ?

          二、 定義與結(jié)構(gòu)

          單例模式又叫做單態(tài)模式或者單件模式。在 GOF 書中給出的定義為:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。單例模式中的 單例 通常用來代表那些本質(zhì)上具有唯一性的系統(tǒng)組件(或者叫做資源)。比如文件系統(tǒng)、資源管理器等等。

          單 例模式的目的就是要控制特定的類只產(chǎn)生一個(gè)對(duì)象,當(dāng)然也允許在一定情況下靈活的改變對(duì)象的個(gè)數(shù)。那么怎么來實(shí)現(xiàn)單例模式呢?一個(gè)類的對(duì)象的產(chǎn)生是由類構(gòu)造 函數(shù)來完成的,如果想限制對(duì)象的產(chǎn)生,就要將構(gòu)造函數(shù)變?yōu)樗接械模ㄖ辽偈鞘鼙Wo(hù)的),使得外面的類不能通過引用來產(chǎn)生對(duì)象;同時(shí)為了保證類的可用性,就必 須提供一個(gè)自己的對(duì)象以及訪問這個(gè)對(duì)象的靜態(tài)方法。

          現(xiàn)在對(duì)單例模式有了大概的了解了吧,其實(shí)單例模式在實(shí)現(xiàn)上是非常簡(jiǎn)單的 —— 只有一個(gè)角色,而客戶則通過調(diào)用類方法來得到類的對(duì)象。

          放上一個(gè)類圖吧,這樣更直觀一些:

          ?

          單例模式可分為有狀態(tài)的和無狀態(tài)的。有狀態(tài)的單例對(duì)象一般也是可變的單例對(duì)象, 多個(gè)單態(tài)對(duì)象在一起就可以作為一個(gè)狀態(tài)倉(cāng)庫(kù)一樣向外提供服務(wù)。沒有狀態(tài)的單例對(duì)象也就是不變單例對(duì)象,僅用做提供工具函數(shù)。

          ?

          三、 實(shí)現(xiàn)

          在單例模式的實(shí)現(xiàn)上有幾種不同的方式,我在這里將一一講解。先來看一種方式,它在《 java 與模式》中被稱為餓漢式。

          ?

          public class Singleton {

          // 在自己內(nèi)部定義自己一個(gè)實(shí)例

          // 注意這是 private 只供內(nèi)部調(diào)用

          private static Singleton instance = new Singleton();

          // 如上面所述,將構(gòu)造函數(shù)設(shè)置為私有

          private Singleton(){

          }  

          // 靜態(tài)工廠方法, 提供了一個(gè)供外部訪問得到對(duì)象 的靜態(tài)方法  
             public static Singleton getInstance() {
               return instance;   
             }
          }

          ?

          ?????? 下面這種方式被稱為懶漢式: P

          ?

          public class Singleton {

          ?????? // 和上面有什么不同?

          private static Singleton instance = null;

          // 設(shè)置為私有的構(gòu)造函數(shù)

          private Singleton(){

          }  

          // 靜態(tài)工廠方法

          public static synchronized Singleton getInstance() {

          // 這個(gè)方法比上面有所改進(jìn)
             ?????? if (instance==null)

          ?????? ?????? instance new Singleton();
             ?????? return instance;   

          }

          }

          ?

          先讓我們來比較一下這兩種實(shí)現(xiàn)方式。

          首先他們的構(gòu)造函數(shù)都是私有的,徹底斷開了使用構(gòu)造函數(shù)來得到類的實(shí)例的通道,但是這樣也使得類失去了多態(tài)性(大概這就是為什么有人將這種模式稱作單態(tài)模式)。 ?

          在第二種方式中,對(duì)靜態(tài)工廠方法進(jìn)行了同步處理,原因很明顯——為了防止多線程環(huán)境中產(chǎn)生多個(gè)實(shí)例;而在第一種方式中則不存在這種情況。

          ?????? 在第二種方式中將類對(duì)自己的實(shí)例化延遲到第一次被引用的時(shí)候。而在第一種方式中則是在類被加載的時(shí)候?qū)嵗@樣多次加載會(huì)照成多次實(shí)例化。但是第二種方式由于使用了同步處理,在反應(yīng)速度上要比第一種慢一些。

          ?????? java 與模式》書中提到,就 java 語言來說,第一種方式更符合 java 語言本身的特點(diǎn)。

          ?????? 以 上兩種實(shí)現(xiàn)方式均失去了多態(tài)性,不允許被繼承。還有另外一種靈活點(diǎn)的實(shí)現(xiàn),將構(gòu)造函數(shù)設(shè)置為受保護(hù)的,這樣允許被繼承產(chǎn)生子類。這種方式在具體實(shí)現(xiàn)上又有 所不同,可以將父類中獲得對(duì)象的靜態(tài)方法放到子類中再實(shí)現(xiàn);也可以在父類的靜態(tài)方法中進(jìn)行條件判斷來決定獲得哪一個(gè)對(duì)象;在 GOF 中認(rèn)為最好的一種方式是維護(hù)一張存有對(duì)象和對(duì)應(yīng)名稱的注冊(cè)表(可以使用 HashMap 來實(shí)現(xiàn))。下面的實(shí)現(xiàn)參考《 java 與模式》采用帶有注冊(cè)表的方式。

          ?

          import java.util.HashMap;

          ?

          public class Singleton

          {

          // 用來存放對(duì)應(yīng)關(guān)系

          ?????? private static HashMap sinRegistry = new HashMap();

          ?????? static private Singleton s = new Singleton();

          ?????? // 受保護(hù)的構(gòu)造函數(shù)

          ?????? protected Singleton()

          ?????? {}

          ?????? public static Singleton getInstance(String name)

          ?????? {

          ????????????? if(name == null)

          ???????????????????? name = "Singleton";

          ????????????? if(sinRegistry.get(name)==null)

          ????????????? {

          ???????????????????? try{

          ??????????????????????????? sinRegistry.put(name , Class.forName(name).newInstance());

          ???????????????????? }catch(Exception e)

          ???????????????????? {

          ??????????????????????????? e.printStackTrace();

          ???????????????????? }?????

          ????????????? }

          ????????????? return (Singleton)(sinRegistry.get(name));?

          ?????? }

          ?????? public void test()

          ?????? {

          ????????????? System.out.println("getclasssuccess!");??????

          ?????? }

          }

          ?

          public class SingletonChild1 extends Singleton

          {

          ?????? public SingletonChild1(){}

          ?????? static ?? public SingletonChild1 getInstance()

          ?????? {

          ????????????? return (SingletonChild1)Singleton.getInstance("SingletonChild1");?????

          ?????? }

          ?????? public void test()

          ?????? {

          ????????????? System.out.println("getclasssuccess111!");?

          ?????? }

          }

          ?

          java 中子類的構(gòu)造函數(shù)的范圍不能比父類的小,所以可能存在不守規(guī)則的客戶程序使用其構(gòu)造函數(shù)來產(chǎn)生實(shí)例。

          ?

          四、單例模式邪惡論

          看這題目也許有點(diǎn)夸張,不過這對(duì)初學(xué)者是一個(gè)很好的警告。單例模式在 java 中的使用存在很多陷阱和假象,這使得沒有意識(shí)到單例模式使用局限性的你在系統(tǒng)中布下了隱患……

          其實(shí)這個(gè)問題早在 2001 年的時(shí)候就有人在網(wǎng)上系統(tǒng)的提出來過,我在這里只是老生常談了。但是對(duì)于大多的初學(xué)者來說,可能這樣的觀點(diǎn)在還很陌生。下面我就一一列舉出單例模式在 java 中存在的陷阱。

          ?

          多個(gè)虛擬機(jī)

          當(dāng)系統(tǒng)中的單例類被拷貝運(yùn)行在多個(gè)虛擬機(jī)下的時(shí)候,在每一個(gè)虛擬機(jī)下都可以創(chuàng)建一個(gè)實(shí)例對(duì)象。在使用了 EJB JINI RMI 技術(shù)的分布式系統(tǒng)中,由于中間件屏蔽掉了分布式系統(tǒng)在物理上的差異,所以對(duì)你來說,想知道具體哪個(gè)虛擬機(jī)下運(yùn)行著哪個(gè)單例對(duì)象是很困難的。

          因此,在使用以上分布技術(shù)的系統(tǒng)中,應(yīng)該避免使用存在狀態(tài)的單例模式,因?yàn)橐粋€(gè)有狀態(tài)的單例類,在不同虛擬機(jī)上,各個(gè)單例對(duì)象保存的狀態(tài)很可能是不一樣的,問題也就隨之產(chǎn)生。而且在 EJB 中不要使用單例模式來控制訪問資源,因?yàn)檫@是由 EJB 容器來負(fù)責(zé)的。在其它的分布式系統(tǒng)中,當(dāng)每一個(gè)虛擬機(jī)中的資源是不同的時(shí)候,可以考慮使用單例模式來進(jìn)行管理。

          ?

          多個(gè)類加載器

          當(dāng)存在多個(gè)類加載器加載類的時(shí)候,即使它們加載的是相同包名,相同類名甚至每個(gè)字節(jié)都完全相同的類,也會(huì)被區(qū)別對(duì)待的。因?yàn)椴煌念惣虞d器會(huì)使用不同的命名空間( namespace )來區(qū)分同一個(gè)類。因此,單例類在多加載器的環(huán)境下會(huì)產(chǎn)生多個(gè)單例對(duì)象。

          也許你認(rèn)為出現(xiàn)多個(gè)類加載器的情況并不是很多。其實(shí)多個(gè)類加載器存在的情況并不少見。在很多 J2EE 服務(wù)器上允許存在多個(gè) servlet 引擎,而每個(gè)引擎是采用不同的類加載器的;瀏覽器中 applet 小程序通過網(wǎng)絡(luò)加載類的時(shí)候,由于安全因素,采用的是特殊的類加載器,等等。

          ?????? 這種情況下,由狀態(tài)的單例模式也會(huì)給系統(tǒng)帶來隱患。因此除非系統(tǒng)由協(xié)調(diào)機(jī)制,在一般情況下不要使用存在狀態(tài)的單例模式。

          ?

          ?????? 錯(cuò)誤的同步處理

          ?????? 在使用上面介紹的懶漢式單例模式時(shí),同步處理的恰當(dāng)與否也是至關(guān)重要的。不然可能會(huì)達(dá)不到得到單個(gè)對(duì)象的效果,還可能引發(fā)死鎖等錯(cuò)誤。因此在使用懶漢式單例模式時(shí)一定要對(duì)同步有所了解。不過使用餓漢式單例模式就可以避免這個(gè)問題。

          ?

          ?????? 子類破壞了對(duì)象控制

          ?????? 在上一節(jié)介紹最后一種擴(kuò)展性較好的單例模式實(shí)現(xiàn)方式的時(shí)候,就提到,由于類構(gòu)造函數(shù)變得不再私有,就有可能失去對(duì)對(duì)象的控制。這種情況只能通過良好的文檔來規(guī)范。

          ?

          ?????? 串行化(可序列化)

          為了使一個(gè)單例類變成可串行化的,僅僅在聲明中添加“ implements Serializable ”是不夠的。因?yàn)橐粋€(gè)串行化的對(duì)象在每次返串行化的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的對(duì)象,而不僅僅是一個(gè)對(duì)原有對(duì)象的引用。為了防止這種情況,可以在單例類中加入 readResolve 方法。 關(guān)于這個(gè)方法的具體情況請(qǐng)參考《 Effective Java 》一書第 57 條建議。

          其實(shí)對(duì)象的串行化并不僅局限于上述方式,還存在基于 XML 格式的對(duì)象串行化方式。這種方式也存在上述的問題,所以在使用的時(shí)候要格外小心。

          ?

          ?????? 上面羅列了一些使用單例模式時(shí)可能會(huì)遇到的問題。而且這些問題都和 java 中的類、線程、虛擬機(jī)等基礎(chǔ)而又復(fù)雜的概念交織在一起,你如果稍不留神……。但是這并不代表著單例模式就一無是處,更不能一棒子將其打死。它還是不可缺少的一種基礎(chǔ)設(shè)計(jì)模式,它對(duì)一些問題提供了非常有效的解決方案,在 java 中你完全可以把它看成編碼規(guī)范來學(xué)習(xí),只是使用的時(shí)候要考慮周全些就可以了。

          ?

          五、 題外話

          拋開單例模式,使用下面一種簡(jiǎn)單的方式也能得到單例,而且如果你確信此類永遠(yuǎn)是單例的,使用下面這種方式也許更好一些。

          public static final Singleton INSTANCE = new Singleton();

          而使用單例模式提供的方式,這可以在不改變 API 的情況下,改變我們對(duì)單例類的具體要求。

          ?

          六、 總結(jié)

          竭盡所能寫下了關(guān)于單例模式比較詳細(xì)的介紹,請(qǐng)大家指正。
          posted on 2006-08-29 19:05 Alex 閱讀(376) 評(píng)論(0)  編輯  收藏 所屬分類: design
          主站蜘蛛池模板: 台北县| 筠连县| 行唐县| 苍山县| 五原县| 旬邑县| 马尔康县| 喀什市| 金塔县| 阳城县| 白朗县| 肥乡县| 石门县| 广汉市| 托克逊县| 上蔡县| 堆龙德庆县| 平南县| 天气| 拜泉县| 周宁县| 武陟县| 龙岩市| 齐齐哈尔市| 甘泉县| 怀安县| 乌兰浩特市| 安化县| 辉南县| 金沙县| 渑池县| 怀安县| 黄梅县| 肇州县| 东兰县| 墨竹工卡县| 许昌市| 托里县| 梨树县| 平度市| 遵化市|