作為對象的創建模式[GOF] 單例模式確保某一個類只有一個實例而且自行實例化並向整個系統提供這個實例這個類稱為單例類
單例模式的要點
單例單例
顯然單例模式的要點有三個一是某各類只能有一個實例二是它必須自行創建這個事例三是它必須自行向整個系統提供這個實例在下面的對象圖中有一個單例對象而客戶甲客戶乙 和客戶丙是單例對象的三個客戶對象可以看到所有的客戶對象共享一個單例對象而且從單例對象到自身的連接線可以看出單例對象持有對自己的引用
資源管理
一些資源管理器常常設計成單例模式
在計算機系統中需要管理的資源包括軟件外部資源譬如每台計算機可以有若干個打印機但只能有一個Printer Spooler 以避免兩個打印作業同時輸出到打印機中每台計算機可以有若干傳真卡但是只應該有一個軟件負責管理傳真卡以避免出現兩份傳真作業同時傳到傳真卡中的情況每台計算機可以有若干通信端口系統應當集中管理這些通信端口以避免一個通信端口同時被兩個請求同時調用
需要管理的資源包括軟件內部資源譬如大多數的軟件都有一個(甚至多個)屬性(properties)文件存放系統配置這樣的系統應當由一個對象來管理一個屬性文件
需要管理的軟件內部資源也包括譬如負責記錄網站來訪人數的部件記錄軟件系統內部事件出錯信息的部件或是對系統的表現進行檢查的部件等這些部件都必須集中管理不可政出多頭
這些資源管理器構件必須只有一個實例這是其一它們必須自行初始化這是其二允許整個系統訪問自己這是其三因此它們都滿足單例模式的條件是單例模式的應用
一個例子Windows 回收站
Windows x 以後的視窗系統中都有一個回收站下圖就顯示了Windows 的回收站
在整個視窗系統中回收站只能有一個實例整個系統都使用這個惟一的實例而且回收站自行提供自己的實例因此回收站是單例模式的應用
雙重檢查成例
在本章最後的附錄裡研究了雙重檢查成例雙重檢查成例與單例模式並無直接的關系但是由於很多C 語言設計師在單例模式裡面使用雙重檢查成例所以這一做法也被很多Java 設計師所模仿因此本書在附錄裡提醒讀者雙重檢查成例在Java 語言裡並不能成立詳情請見本章的附錄
單例模式的結構
單例模式有以下的特點
…… 單例類只可有一個實例
…… 單例類必須自己創建自己這惟一的實例
…… 單例類必須給所有其他對象提供這一實例
雖然單例模式中的單例類被限定只能有一個實例但是單例模式和單例類可以很容易被推廣到任意且有限多個實例的情況這時候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class)請見專題多例(Multiton )模式與多語言支持一章單例類的簡略類圖如下所示
由於Java 語言的特點使得單例模式在Java 語言的實現上有自己的特點這些特點主要表現在單例類如何將自己實例化上
餓漢式單例類餓漢式單例類是在Java 語言裡實現得最為簡便的單例類下面所示的類圖描述了一個餓漢式單例類的典型實現
從圖中可以看出此類已經自已將自己實例化
代碼清單餓漢式單例類
public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton()/** * 私有的默認構造子*/ private EagerSingleton() { } /** * 靜態工廠方法*/ public static EagerSingleton getInstance()
{
Java 與模式return m_instance}
讀者可以看出在這個類被加載時靜態變量m_instance 會被初始化此時類的私有構造子會被調用這時候單例類的惟一實例就被創建出來了
Java 語言中單例類的一個最重要的特點是類的構造子是私有的從而避免外界利用構造子直接創建出任意多的實例值得指出的是由於構造子是私有的因此此類不能被繼承
懶漢式單例類
與餓漢式單例類相同之處是類的構造子是私有的與餓漢式單例類不同的是懶漢式單例類在第一次被引用時將自己實例化如果加載器是靜態的那麼在懶漢式單例類被加載時不會將自己實例化如下圖所示類圖中給出了一個典型的餓漢式單例類實現
代碼清單懶漢式單例類
package comjavapatternssingletondemospublic class LazySingleton { private static LazySingleton m_instance = null/** * 私有的默認構造子保證外界無法直接實例化*/ private LazySingleton() { } /** * 靜態工廠方法返還此類的惟一實例*/ synchronized public static LazySingleton getInstance()
{ if (m_instance == null)
{ m_instance = new LazySingleton()} return m_instance}
讀者可能會注意到在上面給出懶漢式單例類實現裡對靜態工廠方法使用了同步化以處理多線程環境有些設計師在這裡建議使用所謂的雙重檢查成例必須指出的是雙重檢查成例不可以在Java 語言中使用不十分熟悉的讀者可以看看後面給出的小節
同樣由於構造子是私有的因此此類不能被繼承餓漢式單例類在自己被加載時就將自己實例化即便加載器是靜態的在餓漢式單例類被加載時仍會將自己實例化單從資源利用效率角度來講這個比懶漢式單例類稍差些
從速度和反應時間角度來講則比懶漢式單例類稍好些然而懶漢式單例類在實例化時 必須處理好在多個線程同時首次引用此類時的訪問限制問題特別是當單例類作為資源控制器在實例化時必然涉及資源初始化而資源初始化很有可能耗費時間這意味著出現多線程同時首次引用此類的機率變得較大
餓漢式單例類可以在Java 語言內實現 但不易在C++ 內實現因為靜態初始化在C++ 裡沒有固定的順序因而靜態的m_instance 變量的初始化與類的加載順序沒有保證可能會出問題這就是為什麼GoF 在提出單例類的概念時舉的例子是懶漢式的他們的書影響之大以致Java 語言中單例類的例子也大多是懶漢式的實際上本書認為餓漢式單例類更符合Java 語言本身的特點
登記式單例類
登記式單例類是GoF 為了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點而設計的本書把他們的例子翻譯為Java 語言並將它自己實例化的方式從懶漢式改為餓漢式只是它的子類實例化的方式只能是懶漢式的 這是無法改變的如下圖所示是登記式單例類的一個例子圖中的關系線表明此類已將自己實例化
代碼清單登記式單例類
import javautilHashMappublic class RegSingleton { static private HashMap m_registry = new HashMap()static { RegSingleton x = new RegSingleton()m_registryput( xgetClass()getName() x)} /** * 保護的默認構造子*/ protected RegSingleton() {} /** * 靜態工廠方法返還此類惟一的實例*/ static public RegSingleton getInstance(String name)
{ if (name == null)
{ name = comjavapatternssingletondemosRegSingleton} if (m_registryget(name) == null)
{ try { m_registryput( nameClassforName(name)newInstance() ) } catch(Exception e)
{ Systemoutprintln(Error happened)} return (RegSingleton) (m_registryget(name) )} /** * 一個示意性的商業方法*/ public String about()
{ return Hello I am RegSingleton}它的子類RegSingletonChild 需要父類的幫助才能實例化下圖所示是登記式單例類子類的一個例子圖中的關系表明此類是由父類將子類實例化的
下面是子類的源代碼
代碼清單登記式單例類的子類
import javautilHashMappublic class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 靜態工廠方法*/ static public RegSingletonChild getInstance()
{ return (RegSingletonChild)
RegSingletongetInstance(comjavapatternssingletondemosRegSingletonChild )} /** * 一個示意性的商業方法*/ public String about()
{ return Hello I am RegSingletonChild}
在GoF 原始的例子中並沒有getInstance() 方法這樣得到子類必須調用的getInstance(String name)方法並傳入子類的名字因此很不方便本章在登記式單例類子類的例子裡加入了getInstance() 方法這樣做的好處是RegSingletonChild 可以通過這個方法返還自已的實例而這樣做的缺點是由於數據類型不同無法在RegSingleton 提供這樣一個方法由於子類必須允許父類以構造子調用產生實例因此它的構造子必須是公開的這樣一來就等於允許了以這樣方式產生實例而不在父類的登記中這是登記式單例類的一個缺點
GoF 曾指出由於父類的實例必須存在才可能有子類的實例這在有些情況下是一個浪費這是登記式單例類的另一個缺點
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27416.html