Dict.CN 在線詞典, 英語(yǔ)學(xué)習(xí), 在線翻譯

          都市淘沙者

          荔枝FM Everyone can be host

          統(tǒng)計(jì)

          留言簿(23)

          積分與排名

          優(yōu)秀學(xué)習(xí)網(wǎng)站

          友情連接

          閱讀排行榜

          評(píng)論排行榜

          Java模式設(shè)計(jì)之單例模式

          作為對(duì)象的創(chuàng)建模式[GOF95], 單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為單例類。
          注:本文乃閻宏博士的《Java與模式》一書(shū)的第十五章。

            引言

            單例模式的要點(diǎn)

            單例單例

            顯然單例模式的要點(diǎn)有三個(gè);一是某各類只能有一個(gè)實(shí)例;二是它必須自行創(chuàng)建這個(gè)事例;三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在下面的對(duì)象圖中,有一個(gè)"單例對(duì)象",而"客戶甲"、"客戶乙" 和"客戶丙"是單例對(duì)象的三個(gè)客戶對(duì)象。可以看到,所有的客戶對(duì)象共享一個(gè)單例對(duì)象。而且從單例對(duì)象到自身的連接線可以看出,單例對(duì)象持有對(duì)自己的引用。



            資源管理

            一些資源管理器常常設(shè)計(jì)成單例模式。

            在計(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)當(dāng)由一個(gè)對(duì)象來(lái)管理一個(gè)屬性文件。

            需要管理的軟件內(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)用。

            一個(gè)例子:Windows 回收站

            Windows 9x 以后的視窗系統(tǒng)中都有一個(gè)回收站,下圖就顯示了Windows 2000 的回收站。



            在整個(gè)視窗系統(tǒng)中,回收站只能有一個(gè)實(shí)例,整個(gè)系統(tǒng)都使用這個(gè)惟一的實(shí)例,而且回收站自行提供自己的實(shí)例。因此,回收站是單例模式的應(yīng)用。

            雙重檢查成例

            在本章最后的附錄里研究了雙重檢查成例。雙重檢查成例與單例模式并無(wú)直接的關(guān)系,但是由于很多C 語(yǔ)言設(shè)計(jì)師在單例模式里面使用雙重檢查成例,所以這一做法也被很多Java 設(shè)計(jì)師所模仿。因此,本書(shū)在附錄里提醒讀者,雙重檢查成例在Java 語(yǔ)言里并不能成立,詳情請(qǐng)見(jiàn)本章的附錄。

          單例模式有以下的特點(diǎn):

             .. 單例類只可有一個(gè)實(shí)例。

             .. 單例類必須自己創(chuàng)建自己這惟一的實(shí)例。

             .. 單例類必須給所有其他對(duì)象提供這一實(shí)例。

            雖然單例模式中的單例類被限定只能有一個(gè)實(shí)例,但是單例模式和單例類可以很容易被推廣到任意且有限多個(gè)實(shí)例的情況,這時(shí)候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class),請(qǐng)見(jiàn)"專題:多例(Multiton )模式與多語(yǔ)言支持"一章。單例類的簡(jiǎn)略類圖如下所示。



            由于Java 語(yǔ)言的特點(diǎn),使得單例模式在Java 語(yǔ)言的實(shí)現(xiàn)上有自己的特點(diǎn)。這些特點(diǎn)主要表現(xiàn)在單例類如何將自己實(shí)例化上。

            餓漢式單例類餓漢式單例類是在Java 語(yǔ)言里實(shí)現(xiàn)得最為簡(jiǎn)便的單例類,下面所示的類圖描述了一個(gè)餓漢式單例類的典型實(shí)現(xiàn)。



            從圖中可以看出,此類已經(jīng)自已將自己實(shí)例化。

            代碼清單1:餓漢式單例類

          public class EagerSingleton
          {
          private static final EagerSingleton m_instance =
          new EagerSingleton();
          /**
          * 私有的默認(rèn)構(gòu)造子
          */
          private EagerSingleton() { }
          /**
          * 靜態(tài)工廠方法
          */
          public static EagerSingleton getInstance()
          {

          ·224·Java 與模式
          return m_instance;
          }
          }

            讀者可以看出,在這個(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í)例。值得指出的是,由于構(gòu)造子是私有的,因此,此類不能被繼承。

          懶漢式單例類

            與餓漢式單例類相同之處是,類的構(gòu)造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時(shí)將自己實(shí)例化。如果加載器是靜態(tài)的,那么在懶漢式單例類被加載時(shí)不會(huì)將自己實(shí)例化。如下圖所示,類圖中給出了一個(gè)典型的餓漢式單例類實(shí)現(xiàn)。



            代碼清單2:懶漢式單例類

          package com.javapatterns.singleton.demos;
          public class LazySingleton
          {
          private static LazySingleton
          m_instance = null;
          /**
          * 私有的默認(rèn)構(gòu)造子,保證外界無(wú)法直接實(shí)例化
          */
          private LazySingleton() { }
          /**
          * 靜態(tài)工廠方法,返還此類的惟一實(shí)例
          */
          synchronized public static LazySingleton
          getInstance()
          {
          if (m_instance == null)
          {
          m_instance = new LazySingleton();
          }
          return m_instance;
          }
          }

            讀者可能會(huì)注意到,在上面給出懶漢式單例類實(shí)現(xiàn)里對(duì)靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。有些設(shè)計(jì)師在這里建議使用所謂的"雙重檢查成例"。必須指出的是,"雙重檢查成例"不可以在Java 語(yǔ)言中使用。不十分熟悉的讀者,可以看看后面給出的小節(jié)。

            同樣,由于構(gòu)造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時(shí)就將自己實(shí)例化。即便加載器是靜態(tài)的,在餓漢式單例類被加載時(shí)仍會(huì)將自己實(shí)例化。單從資源利用效率角度來(lái)講,這個(gè)比懶漢式單例類稍差些。

            從速度和反應(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)有固定的順序,因而靜態(tài)的m_instance 變量的初始化與類的加載順序沒(méi)有保證,可能會(huì)出問(wèn)題。這就是為什么GoF 在提出單例類的概念時(shí),舉的例子是懶漢式的。他們的書(shū)影響之大,以致Java 語(yǔ)言中單例類的例子也大多是懶漢式的。實(shí)際上,本書(shū)認(rèn)為餓漢式單例類更符合Java 語(yǔ)言本身的特點(diǎn)。


          登記式單例類

            登記式單例類是GoF 為了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點(diǎn)而設(shè)計(jì)的。本書(shū)把他們的例子翻譯為Java 語(yǔ)言,并將它自己實(shí)例化的方式從懶漢式改為餓漢式。只是它的子類實(shí)例化的方式只能是懶漢式的, 這是無(wú)法改變的。如下圖所示是登記式單例類的一個(gè)例子,圖中的關(guān)系線表明,此類已將自己實(shí)例化。



            代碼清單3:登記式單例類

          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 getInstance(String name)
          {
          if (name == null)
          {
          name = "com.javapatterns.singleton.demos.RegSingleton";
          }
          if (m_registry.get(name) == null)
          {
          try
          {
          m_registry.put( name,
          Class.forName(name).newInstance() ) ;
          }
          catch(Exception e)
          {
          System.out.println("Error happened.");
          }
          }
          return (RegSingleton) (m_registry.get(name) );
          }
          /**
          * 一個(gè)示意性的商業(yè)方法
          */
          public String about()
          {
          return "Hello, I am RegSingleton.";
          }
          }

            它的子類RegSingletonChild 需要父類的幫助才能實(shí)例化。下圖所示是登記式單例類子類的一個(gè)例子。圖中的關(guān)系表明,此類是由父類將子類實(shí)例化的。



            下面是子類的源代碼。

            代碼清單4:登記式單例類的子類

          import java.util.HashMap;
          public class RegSingletonChild extends RegSingleton
          {
          public RegSingletonChild() {}
          /**
          * 靜態(tài)工廠方法
          */
          static public RegSingletonChild getInstance()
          {
          return (RegSingletonChild)
          RegSingleton.getInstance(
          "com.javapatterns.singleton.demos.RegSingletonChild" );
          }
          /**
          * 一個(gè)示意性的商業(yè)方法
          */
          public String about()
          {
          return "Hello, I am RegSingletonChild.";
          }
          }

            在GoF 原始的例子中,并沒(méi)有g(shù)etInstance() 方法,這樣得到子類必須調(diào)用的getInstance(String name)方法并傳入子類的名字,因此很不方便。本章在登記式單例類子類的例子里,加入了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)。












          posted on 2007-05-21 12:33 都市淘沙者 閱讀(135) 評(píng)論(0)  編輯  收藏 所屬分類: Java Basic/Lucene/開(kāi)源資料

          主站蜘蛛池模板: 榆中县| 宜兴市| 祁门县| 闻喜县| 台安县| 乌鲁木齐县| 清水河县| 大英县| 望奎县| 桑植县| 赞皇县| 扎兰屯市| 陆河县| 凤凰县| 盐边县| 九寨沟县| 化隆| 乌什县| 高台县| 西吉县| 曲麻莱县| 镇安县| 乐都县| 肇庆市| 前郭尔| 塔城市| 嫩江县| 祁连县| 和林格尔县| 舟曲县| 句容市| 三亚市| 家居| 三门峡市| 荃湾区| 城步| 伊春市| 丹江口市| 措美县| 天津市| 海淀区|