設計模式系列之01-單例模式(Singleton Pattern)

概述

Singleton模式要求一個類有且僅有一個實例,並且提供了一個全局的訪問點。這就提出了一個問題:如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?客戶程序在調用某一個類時,它是不會考慮這個類是否只能有一個實例等問題的,所以,這應該是類設計者的責任,而不是類使用者的責任。

從另一個角度來說,Singleton模式其實也是一種職責型模式。因爲我們創建了一個對象,這個對象扮演了獨一無二的角色,在這個單獨的對象實例中,它集中了它所屬類的所有權力,同時它也肩負了行使這種權力的職責!

意圖

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

模型圖

邏輯模型圖:

物理模型圖:

生活中的例子

美國總統的職位是Singleton,美國憲法規定了總統的選舉,任期以及繼任的順序。這樣,在任何時刻只能由一個現任的總統。無論現任總統的身份爲何,其頭銜"美利堅合衆國總統"是訪問這個職位的人的一個全局的訪問點。

五種實現

1.簡單實現

 

 

 1public sealed class Singleton
 2{
 3     static Singleton instance=null;
 4
 5     Singleton()
 6     {
 7     }
 8
 9     public static Singleton Instance
10     {
11         get
12         {
13             if (instance==null)
14             {
15                 instance = new Singleton();
16             }
17             return instance;
18         }
19     }
20 }

這種方式的實現對於線程來說並不是安全的,因爲在多線程的環境下有可能得到Singleton類的多個實例。如果同時有兩個線程去判斷(instance == null),並且得到的結果爲真,這時兩個線程都會創建類Singleton的實例,這樣就違背了Singleton模式的原則。實際上在上述代碼中,有可能在計算出表達式的值之前,對象實例已經被創建,但是內存模型並不能保證對象實例在第二個線程創建之前被發現。

 

該實現方式主要有兩個優點:

l        由於實例是在Instance屬性方法內部創建的,因此類可以使用附加功能(例如,對子類進行實例化),即使它可能引入不想要的依賴性。

l        直到對象要求產生一個實例才執行實例化;這種方法稱爲“惰性實例化”。惰性實例化避免了在應用程序啓動時實例化不必要的 singleton

2.安全的線程

 1public sealed class Singleton
 2{
 3     static Singleton instance=null;
 4     static readonly object padlock = new object();
 5
 6     Singleton()
 7     {
 8     }
 9
10     public static Singleton Instance
11     {
12         get
13         {
14             lock (padlock)
15             {
16                 if (instance==null)
17                 {
18                     instance = new Singleton();
19                 }
20                 return instance;
21             }
22         }
23     }
24 }
25
26

 

這種方式的實現對於線程來說是安全的。我們首先創建了一個進程輔助對象,線程在進入時先對輔助對象加鎖然後再檢測對象是否被創建,這樣可以確保只有一個實例被創建,因爲在同一個時刻加了鎖的那部分程序只有一個線程可以進入。這種情況下,對象實例由最先進入的那個線程創建,後來的線程在進入時(instence == null)爲假,不會再去創建對象實例了。但是這種實現方式增加了額外的開銷,損失了性能。

3.雙重鎖定

 1public sealed class Singleton
 2{
 3     static Singleton instance=null;
 4     static readonly object padlock = new object();
 5
 6     Singleton()
 7     {
 8     }
 9
10     public static Singleton Instance
11     {
12         get
13         {
14             if (instance==null)
15             {
16                 lock (padlock)
17                 {
18                     if (instance==null)
19                     {
20                         instance = new Singleton();
21                     }
22                 }
23             }
24             return instance;
25         }
26     }
27 }
28

這種實現方式對多線程來說是安全的,同時線程不是每次都加鎖,只有判斷對象實例沒有被創建時它才加鎖,有了我們上面第一部分的裏面的分析,我們知道,加鎖後還得再進行對象是否已被創建的判斷。它解決了線程併發問題,同時避免在每個Instance屬性方法的調用中都出現獨佔鎖定。它還允許您將實例化延遲到第一次訪問對象時發生。實際上,應用程序很少需要這種類型的實現。大多數情況下我們會用靜態初始化。這種方式仍然有很多缺點:無法實現延遲初始化。

4.靜態初始化

 1public sealed class Singleton
 2{
 3     static readonly Singleton instance=new Singleton();
 4
 5     static Singleton()
 6     {
 7     }
 8
 9     Singleton()
10     {
11     }
12
13     public static Singleton Instance
14     {
15         get
16         {
17             return instance;
18         }
19     }
20 }
21

看到上面這段富有戲劇性的代碼,我們可能會產生懷疑,這還是Singleton模式嗎?在此實現中,將在第一次引用類的任何成員時創建實例。公共語言運行庫負責處理變量初始化。該類標記爲sealed以阻止發生派生,而派生可能會增加實例。此外,變量標記爲 readonly,這意味着只能在靜態初始化期間(此處顯示的示例)或在類構造函數中分配變量。

該實現與前面的示例類似,不同之處在於它依賴公共語言運行庫來初始化變量。它仍然可以用來解決 Singleton模式試圖解決的兩個基本問題:全局訪問和實例化控制。公共靜態屬性爲訪問實例提供了一個全局訪問點。此外,由於構造函數是私有的,因此不能在類本身以外實例化Singleton類;因此,變量引用的是可以在系統中存在的唯一的實例。

由於 Singleton實例被私有靜態成員變量引用,因此在類首次被對Instance屬性的調用所引用之前,不會發生實例化。

這種方法唯一的潛在缺點是,您對實例化機制的控制權較少。在Design Patterns形式中,您能夠在實例化之前使用非默認的構造函數或執行其他任務。由於在此解決方案中由.NET Framework 負責執行初始化,因此您沒有這些選項。在大多數情況下,靜態初始化是在 .NET中實現Singleton的首選方法。

5.延遲初始化

 1public sealed class Singleton
 2 {
 3     Singleton()
 4     {
 5     }
 6
 7     public static Singleton Instance
 8     {
 9         get
10         {
11             return Nested.instance;
12         }
13     }
14     
15     class Nested
16     {
17         static Nested()
18         {
19         }
20
21         internal static readonly Singleton instance = new Singleton();
22     }
23 }
24

這裏,初始化工作有Nested類的一個靜態成員來完成,這樣就實現了延遲初始化,並具有很多的優勢,是值得推薦的一種實

現方式。

實現要點

l       Singleton模式是限制而不是改進類的創建。

l        Singleton類中的實例構造器可以設置爲Protected以允許子類派生。

l        Singleton模式一般不要支持Icloneable接口,因爲這可能導致多個對象實例,與Singleton模式的初衷違背。

l        Singleton模式一般不要支持序列化,這也有可能導致多個對象實例,這也與Singleton模式的初衷違背。

l        Singleton只考慮了對象創建的管理,沒有考慮到銷燬的管理,就支持垃圾回收的平臺和對象的開銷來講,我們一般沒必要對其銷燬進行特殊的管理。

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

 

 

l        可以很簡單的修改一個Singleton,使它有少數幾個實例,這樣做是允許的而且是有意義的。

優點

l        實例控制:Singleton會阻止其他對象實例化其自己的Singleton對象的副本,從而確保所有對象都訪問唯一實例

l        靈活性:因爲類控制了實例化過程,所以類可以更加靈活修改實例化過程

缺點

l        開銷:雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題,上面的五種實現方式中已經說過了。

l         可能的開發混淆:使用singleton對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用 new關鍵字實例化對象。因爲可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。

l        對象的生存期:Singleton不能解決刪除單個對象的問題。在提供內存管理的語言中(例如基於 .NET Framework的語言),只有Singleton類能夠導致實例被取消分配,因爲它包含對該實例的私有引用。在某些語言中(如 C++),其他類可以刪除
對象實例,但這樣會導致Singleton類中出現懸浮引用。

適用性

l        當類只能有一個實例而且客戶可以從一個衆所周知的訪問點訪問它時。

l        當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。

應用場景

l        每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,避免兩個打印作業同時輸出到打印機。
(摘自呂震宇的C#設計模式(7)-Singleton Pattern

l        PC機中可能有幾個串口,但只能有一個COM1口的實例。

l        系統中只能有一個窗口管理器。

l        .NET Remoting中服務器激活對象中的Sigleton對象,確保所有的客戶程序的請求都只有一個實例來處理。

完整示例

這是一個簡單的計數器例子,四個線程同時進行計數。

 1using System;
 2using System.Threading;
 3
 4namespace SigletonPattern.SigletonCounter
 5{
 6     /**//// <summary>
 7     /// 功能:簡單計數器的單件模式
 8     /// 編寫:Terrylee
 9     /// 日期:20051206
10     /// </summary>
11     public class CountSigleton
12     {
13         /**////存儲唯一的實例
14         static CountSigleton uniCounter = new CountSigleton();  
15    
16         /**////存儲計數值
17         private int totNum = 0;  
18    
19         private CountSigleton() 
20    
21         
22             /**////線程延遲2000毫秒
23             Thread.Sleep(2000);
24         } 
25    
26         static public CountSigleton Instance() 
27    
28         
29    
30             return uniCounter; 
31    
32         } 
33         
34         /**////計數加1
35         public void Add()
36         
37             totNum ++;
38         }  
39         
40         /**////獲得當前計數值
41         public int GetCounter()
42         
43             return totNum;
44         } 
45
46     }
47 }
48

 

 1using System;
 2using System.Threading;
 3using System.Text;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7     /**//// <summary>
 8     /// 功能:創建一個多線程計數的類
 9     /// 編寫:Terrylee
10     /// 日期:20051206
11     /// </summary>
12     public class CountMutilThread
13     {
14         public CountMutilThread()
15         {
16             
17         }
18
19         /**//// <summary>
20         /// 線程工作
21         /// </summary>
22         public static void DoSomeWork()
23         {
24             /**////構造顯示字符串
25             string results = "";
26
27             /**////創建一個Sigleton實例
28             CountSigleton MyCounter = CountSigleton.Instance();
29
30             /**////循環調用四次
31             for(int i=1;i<5;i++)
32             {
33                 /**////開始計數
34                 MyCounter.Add();
35                 
36                 results +="線程";
37                 results += Thread.CurrentThread.Name.ToString() + "——";
38                 results += "當前的計數:";
39                 results += MyCounter.GetCounter().ToString();
40                 results += "\n";
41
42                 Console.WriteLine(results);
43                 
44                 /**////清空顯示字符串
45                 results = "";
46             }
47         }
48
49         public void StartMain()
50         {
51
52             Thread thread0 = Thread.CurrentThread; 
53    
54             thread0.Name = "Thread 0"; 
55    
56             Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 
57    
58             thread1.Name = "Thread 1"; 
59    
60             Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 
61    
62             thread2.Name = "Thread 2"; 
63    
64             Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 
65    
66             thread3.Name = "Thread 3"; 
67    
68             thread1.Start(); 
69    
70             thread2.Start(); 
71    
72             thread3.Start(); 
73             
74             /**////線程0也只執行和其他線程相同的工作
75             DoSomeWork(); 
76         }
77     }
78 }
79

 

 1using System;
 2using System.Text;
 3using System.Threading;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7     /**//// <summary>
 8     /// 功能:實現多線程計數器的客戶端
 9     /// 編寫:Terrylee
10     /// 日期:20051206
11     /// </summary>
12     public class CountClient
13     {
14         public static void Main(string[] args)
15         {
16        CountMutilThread cmt = new CountMutilThread();
17
18             cmt.StartMain();
19
20             Console.ReadLine();
21         }
22     }
23 }
24

 

 

 

總結

Singleton設計模式是一個非常有用的機制,可用於在面向對象的應用程序中提供單個訪問點。文中通過五種實現方式的比較和一個完整的示例,完成了對Singleton模式的一個總結和探索。用一句廣告詞來概括Singleton模式就是“簡約而不簡單”。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章