C#設計模式之一單例模式(Singleton Pattern)【建立型】

1、引言

     看了李建忠老師的講的設計模式已經有一段時間了(這段時間大概有一年多了),本身尚未寫過本身的、有關設計模式的文章。此次想寫一些關於設計模式的文章,用本身的理解和代碼來寫,算是複習一遍。寫做的過程當中也會多看看其餘大牛的文章,爭取讓本身的理解正確,不然把你們帶跑偏了,就是個人過錯了。今天就開始咱們第一個設計模式,該模式是:【單例模式】,英文名稱:Singleton Pattern,這個模式很簡單,一個類型只須要一個實例,他是建立型的設計模式。爲何叫「建立型」設計模式呢,由於他們有分類。固然了分類的方式不同,分類的結果也就不同。設計模式

    從目的來看:多線程

       -建立型(Creational)模式:負責對象建立函數

       -結構型(Structural)模式:處理類與對象間的組合性能

       -行爲型(Behavioral)模式:類與對象交互中的職責分配學習

   從範圍來看:優化

      -類模式處理類與子類的靜態關係spa

      -對象模式處理對象間的動態關係線程

  以上就是分類的方式,咱們按大多數的分類,採用「從目的來看」的分類來對設計模式進行分類,咱們就開始今天的學習吧。

2、單例模式的介紹

   2.一、動機(Motivate)設計

          在軟件系統中,常常有這樣一些特殊的類,必須保證它們在系統中只存在一個實例,才能確保它們的邏輯正確性、以及良好的效率。code

     如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?

         這應該是類設計者的責任,而不是使用者的責任

   2.二、意圖(Intent)

         保證一個類僅有一個實例,並提供一個該實例的全局訪問點。                                   --《設計模式GoF》

   2.三、結構圖(Structure)

      

  2.四、模式的組成

         (1)、單件實例(Singleton):這個模式裏面只有一個類型,就是Singleton類型,而且這個類只有一個實例,能夠經過Instance()方法獲取該類型的實例。

  2.五、單件模式的代碼實現

         既然是單實例,確定會涉及到多線程的問題,咱們就一步一步的來寫代碼,咱們先看看單線程Singleton模式的實現,代碼以下:

 1 /// <summary>  2 /// 單例模式的實現  3 /// </summary>  4 public sealed class Singleton  5 {  6 // 定義一個靜態變量來保存類的實例  7 private static Singleton uniqueInstance;  8  9 // 定義私有構造函數,使外界不能建立該類實例 10 private Singleton() 11  { 12  } 13 14 /// <summary> 15 /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點 16 /// </summary> 17 /// <returns></returns> 18 public static Singleton GetInstance() 19  { 20 // 若是類的實例不存在則建立,不然直接返回 21 if (uniqueInstance == null) 22  { 23 uniqueInstance = new Singleton(); 24  } 25 return uniqueInstance; 26  } 27 }


    私有的實例構造器是屏蔽外界的調用,上面的單例模式的實如今單線程下確實是完美的,也很好的知足了咱們單線程環境的需求。

    單線程單例模式的幾個要點:

    (1)、Singleton模式中的實例構造器能夠設置爲protected以容許子類派生。

    (2)、Singleton模式通常不要支持ICloneable接口,由於這可能會致使多個對象實例,與Singleton模式的初衷違背。

    (3)、Singleton模式通常不要支持序列化,由於這也有可能致使多個對象實例,一樣與Singleton模式的初衷違背。

    (4)、Singletom模式只考慮到了對象建立的工做,沒有考慮對象銷燬的工做。爲何這樣作呢,由於Net平臺是支持垃圾回收的,因此咱們通常沒有必要對其進行銷燬處理。

    (5)、不能應對多線程環境:在多線程環境下,使用Singleton模式仍然有可能獲得Singleton類的多個實例對象


    若是放在多線程環境下,問題就出來了。由於在兩個線程同時運行GetInstance方法時,此時兩個線程判斷(uniqueInstance ==null)這個條件時都返回真,此時兩個線程就都會建立Singleton的實例,這樣就違背了咱們單例模式初衷了。要想解決這個問題,只要讓GetInstance方法在同一時間只運行一個線程運行就行了,讓咱們看看多線程Singleton模式的實現,代碼以下:

 1     /// <summary>  2 /// 單例模式的實現  3 /// </summary>  4 public sealed class Singleton  5  {  6 // 定義一個靜態變量來保存類的實例  7 private static volatile Singleton uniqueInstance;  8  9 // 定義一個標識確保線程同步 10 private static readonly object locker = new object(); 11 12 // 定義私有構造函數,使外界不能建立該類實例 13 private Singleton() 14  { 15  } 16 17 /// <summary> 18 /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22  { 23 // 當第一個線程運行到這裏時,此時會對locker對象 "加鎖", 24 // 當第二個線程運行該方法時,首先檢測到locker對象爲"加鎖"狀態,該線程就會掛起等待第一個線程解鎖 25 // lock語句運行完以後(即線程運行完以後)會對該對象"解鎖" 26 lock (locker) 27  { 28 // 若是類的實例不存在則建立,不然直接返回 29 if (uniqueInstance == null) 30  { 31 uniqueInstance = new Singleton(); 32  } 33  } 34 35 return uniqueInstance; 36  } 37 }

    上面這種解決方案確實能夠解決多線程的問題,可是上面代碼對於每一個線程都會對線程輔助對象locker加鎖以後再判斷實例是否存在,對於這個操做徹底沒有必要的,由於當第一個線程建立了該類的實例以後,後面的線程此時只須要直接判斷(uniqueInstance==null)爲假,此時徹底不必對線程輔助對象加鎖以後再去判斷,因此上面的實現方式增長了額外的開銷,損失了性能,爲了改進上面實現方式的缺陷,咱們只須要在lock語句前面加一句(uniqueInstance==null)的判斷就能夠避免鎖所增長的額外開銷,這種實現方式咱們就叫它 「雙重鎖定(Double Check)」,下面具體看看實現代碼的:

 1     /// <summary>  2 /// 單例模式的實現  3 /// </summary>  4 public sealed class Singleton  5  {  6 // 定義一個靜態變量來保存類的實例  7 private static volatile Singleton uniqueInstance;  8  9 // 定義一個標識確保線程同步 10 private static readonly object locker = new object(); 11 12 // 定義私有構造函數,使外界不能建立該類實例 13 private Singleton() 14  { 15  } 16 17 /// <summary> 18 /// 定義公有方法提供一個全局訪問點,同時你也能夠定義公有屬性來提供全局訪問點 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22  { 23 // 當第一個線程運行到這裏時,此時會對locker對象 "加鎖", 24 // 當第二個線程運行該方法時,首先檢測到locker對象爲"加鎖"狀態,該線程就會掛起等待第一個線程解鎖 25 // lock語句運行完以後(即線程運行完以後)會對該對象"解鎖" 26 // 雙重鎖定只須要一句判斷就能夠了 27 if (uniqueInstance == null) 28  { 29 lock (locker) 30  { 31 // 若是類的實例不存在則建立,不然直接返回 32 if (uniqueInstance == null) 33  { 34 uniqueInstance = new Singleton(); 35  } 36  } 37  } 38 return uniqueInstance; 39  } 40 }

        volatile修飾:編譯器在編譯代碼的時候會對代碼的順序進行微調,用volatile修飾保證了嚴格意義的順序。一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都當心地從新讀取這個變量的值,而不是使用保存在寄存器裏的備份。

3、C#中實現了單例模式的類

    如今咱們看看,如何使用C#語言的特性來實現單例的Singleton模式。

//Singleton模式的實現 public sealed class Singleton { public static readonly Singleton instance=new Singleton(); private Singleton(){} } 以上是內聯初始化(生成的同時進行初始化)的單例模式,它等同於: public sealed class Singleton { public static readonly Singleton instance; //靜態構造函數,CLR只執行一次 static Singleton() { instance=new Singleton(); } //私有構造函數,防止外界調用 private Singleton(){} }

    內聯初始化實際上是把靜態的字段放到靜態構造器去初始化。只要想訪問靜態字段,一定已經在以前執行了靜態構造器。這樣也可以精確地保證使用的時候必定能拿到實例,若是不使用也不會實例化對象,也就是延時加載的功能。他一樣可以支持多線程環境,由於只可能有一個線程執行靜態構造器,不可能有多個線程去執行靜態構造器,感受就是程序已經自動爲咱們加鎖了。

     它的一點弊端就是它不支持參數化的實例化方法。在.NET裏靜態構造器只能聲明一個,並且必須是無參數的,私有的。所以這種方式只適用於無參數的構造器。

     須要說明的是:HttpContext.Current就是一個單例,他們是經過Singleton的擴展方式實現的,他們的單例也並非覆蓋全部領域,只是針對某些局部領域中,是單例的,不一樣的領域中仍是會有不一樣的實例。

4、Singleton模式的擴展

     (1)、將一個實例擴展到n個實例,例如對象池的實現。(n不是指無限個實例,而是固定的某個數)

     (2)、將new構造器的調用轉移到其餘類中,例如多個類協同工做環境中,某個局部環境只須要擁有某個類的一個實例。

     (3)、理解和擴展Singleton模式的核心是「如何控制用戶使用new對一個類的實例構造器的任意調用」。

5、單例模式的實現要點

      一、Singleton模式是限制而不是改進類的建立。

      二、Singleton類中的實例構造器能夠設置爲Protected以容許子類派生。

      三、Singleton模式通常不要支持Icloneable接口,由於這可能致使多個對象實例,與Singleton模式的初衷違背。

      四、Singleton模式通常不要支持序列化,這也有可能致使多個對象實例,這也與Singleton模式的初衷違背。

      五、Singleton只考慮了對象建立的管理,沒有考慮到銷燬的管理,就支持垃圾回收的平臺和對象的開銷來說,咱們通常不必對其銷燬進行特殊的管理。

      六、理解和擴展Singleton模式的核心是「如何控制用戶使用new對一個類的構造器的任意調用」。

      七、能夠很簡單的修改一個Singleton,使它有少數幾個實例,這樣作是容許的並且是有意義的。

       1】、單例模式的優勢:

             (1)、實例控制:Singleton 會阻止其餘對象實例化其本身的 Singleton 對象的副本,從而確保全部對象都訪問惟一實例

             (2)、靈活性:由於類控制了實例化過程,因此類能夠更加靈活修改實例化過程

        2】、單例模式的缺點:

             (1)、開銷:雖然數量不多,但若是每次對象請求引用時都要檢查是否存在類的實例,將仍然須要一些開銷。能夠經過使用靜態初始化解決此問題。

             (2)、可能的開發混淆:使用 singleton 對象(尤爲在類庫中定義的對象)時,開發人員必須記住本身不能使用 new 關鍵字實例化對象。由於可能沒法訪問庫源代碼,所以應用程序開發人員可能會意外發現本身沒法直接實例化此類。

             (3)、對象的生存期:Singleton 不能解決刪除單個對象的問題。由於它包含對該靜態的私有字段的引用,靜態字段是不能被CLR回收內存的,該實例會和應用程序生命週期同樣長,一直存在。

       3】、單例模式的使用場合:

            (1)、當類只能有一個實例並且客戶能夠從一個衆所周知的訪問點訪問它時。

            (2)、當這個惟一實例應該是經過子類化可擴展的,而且客戶應該無需更改代碼就能使用一個擴展的實例時。

6、總結    到這裏,單例模式就介紹完了,這個模式很簡單,理解起來也不是很難,只要把握住代碼的實現技巧,通常問題都不大,可是要找好使用的時機,若是使用錯誤,一些邏輯錯誤比較難排查。

相關文章
相關標籤/搜索