每日一得

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

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評論 :: 0 Trackbacks

          一、 引子

          單例模式是設計模式中使用很頻繁的一種模式,在各種開源框架、應用系統中多有應用,在我前面的幾篇文章中也結合其它模式使用到了單例模式。這里我們就單例模式進行系統的學習。并對有人提出的 單例模式是邪惡的 這個觀點進行了一定的分析。

          ?

          二、 定義與結構

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

          單 例模式的目的就是要控制特定的類只產生一個對象,當然也允許在一定情況下靈活的改變對象的個數。那么怎么來實現單例模式呢?一個類的對象的產生是由類構造 函數來完成的,如果想限制對象的產生,就要將構造函數變為私有的(至少是受保護的),使得外面的類不能通過引用來產生對象;同時為了保證類的可用性,就必 須提供一個自己的對象以及訪問這個對象的靜態方法。

          現在對單例模式有了大概的了解了吧,其實單例模式在實現上是非常簡單的 —— 只有一個角色,而客戶則通過調用類方法來得到類的對象。

          放上一個類圖吧,這樣更直觀一些:

          ?

          單例模式可分為有狀態的和無狀態的。有狀態的單例對象一般也是可變的單例對象, 多個單態對象在一起就可以作為一個狀態倉庫一樣向外提供服務。沒有狀態的單例對象也就是不變單例對象,僅用做提供工具函數。

          ?

          三、 實現

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

          ?

          public class Singleton {

          // 在自己內部定義自己一個實例

          // 注意這是 private 只供內部調用

          private static Singleton instance = new Singleton();

          // 如上面所述,將構造函數設置為私有

          private Singleton(){

          }  

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

          ?

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

          ?

          public class Singleton {

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

          private static Singleton instance = null;

          // 設置為私有的構造函數

          private Singleton(){

          }  

          // 靜態工廠方法

          public static synchronized Singleton getInstance() {

          // 這個方法比上面有所改進
             ?????? if (instance==null)

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

          }

          }

          ?

          先讓我們來比較一下這兩種實現方式。

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

          在第二種方式中,對靜態工廠方法進行了同步處理,原因很明顯——為了防止多線程環境中產生多個實例;而在第一種方式中則不存在這種情況。

          ?????? 在第二種方式中將類對自己的實例化延遲到第一次被引用的時候。而在第一種方式中則是在類被加載的時候實例化,這樣多次加載會照成多次實例化。但是第二種方式由于使用了同步處理,在反應速度上要比第一種慢一些。

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

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

          ?

          import java.util.HashMap;

          ?

          public class Singleton

          {

          // 用來存放對應關系

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

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

          ?????? // 受保護的構造函數

          ?????? 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 中子類的構造函數的范圍不能比父類的小,所以可能存在不守規則的客戶程序使用其構造函數來產生實例。

          ?

          四、單例模式邪惡論

          看這題目也許有點夸張,不過這對初學者是一個很好的警告。單例模式在 java 中的使用存在很多陷阱和假象,這使得沒有意識到單例模式使用局限性的你在系統中布下了隱患……

          其實這個問題早在 2001 年的時候就有人在網上系統的提出來過,我在這里只是老生常談了。但是對于大多的初學者來說,可能這樣的觀點在還很陌生。下面我就一一列舉出單例模式在 java 中存在的陷阱。

          ?

          多個虛擬機

          當系統中的單例類被拷貝運行在多個虛擬機下的時候,在每一個虛擬機下都可以創建一個實例對象。在使用了 EJB JINI RMI 技術的分布式系統中,由于中間件屏蔽掉了分布式系統在物理上的差異,所以對你來說,想知道具體哪個虛擬機下運行著哪個單例對象是很困難的。

          因此,在使用以上分布技術的系統中,應該避免使用存在狀態的單例模式,因為一個有狀態的單例類,在不同虛擬機上,各個單例對象保存的狀態很可能是不一樣的,問題也就隨之產生。而且在 EJB 中不要使用單例模式來控制訪問資源,因為這是由 EJB 容器來負責的。在其它的分布式系統中,當每一個虛擬機中的資源是不同的時候,可以考慮使用單例模式來進行管理。

          ?

          多個類加載器

          當存在多個類加載器加載類的時候,即使它們加載的是相同包名,相同類名甚至每個字節都完全相同的類,也會被區別對待的。因為不同的類加載器會使用不同的命名空間( namespace )來區分同一個類。因此,單例類在多加載器的環境下會產生多個單例對象。

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

          ?????? 這種情況下,由狀態的單例模式也會給系統帶來隱患。因此除非系統由協調機制,在一般情況下不要使用存在狀態的單例模式。

          ?

          ?????? 錯誤的同步處理

          ?????? 在使用上面介紹的懶漢式單例模式時,同步處理的恰當與否也是至關重要的。不然可能會達不到得到單個對象的效果,還可能引發死鎖等錯誤。因此在使用懶漢式單例模式時一定要對同步有所了解。不過使用餓漢式單例模式就可以避免這個問題。

          ?

          ?????? 子類破壞了對象控制

          ?????? 在上一節介紹最后一種擴展性較好的單例模式實現方式的時候,就提到,由于類構造函數變得不再私有,就有可能失去對對象的控制。這種情況只能通過良好的文檔來規范。

          ?

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

          為了使一個單例類變成可串行化的,僅僅在聲明中添加“ implements Serializable ”是不夠的。因為一個串行化的對象在每次返串行化的時候,都會創建一個新的對象,而不僅僅是一個對原有對象的引用。為了防止這種情況,可以在單例類中加入 readResolve 方法。 關于這個方法的具體情況請參考《 Effective Java 》一書第 57 條建議。

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

          ?

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

          ?

          五、 題外話

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

          public static final Singleton INSTANCE = new Singleton();

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

          ?

          六、 總結

          竭盡所能寫下了關于單例模式比較詳細的介紹,請大家指正。
          posted on 2006-08-29 19:05 Alex 閱讀(375) 評論(0)  編輯  收藏 所屬分類: design
          主站蜘蛛池模板: 阿拉善盟| 秭归县| 龙井市| 友谊县| 宝丰县| 措美县| 石屏县| 贵溪市| 宣武区| 汉寿县| 澳门| 绥芬河市| 资源县| 莒南县| 楚雄市| 通州区| 正宁县| 岳普湖县| 淮南市| 鹤山市| 鄂伦春自治旗| 肥城市| 新巴尔虎左旗| 石嘴山市| 两当县| 江川县| 石首市| 宝应县| 桦甸市| 麦盖提县| 文昌市| 漠河县| 会东县| 长子县| 龙山县| 治县。| 三河市| 厦门市| 五指山市| 措美县| 天津市|