深入淺出單件模式(Sigleton Pattern)
——探索設(shè)計(jì)模式系列之二 Terrylee,2005年12月07日 概述 Sigleton模式要求一個(gè)類有且僅有一個(gè)實(shí)例,,并且提供了一個(gè)全局的訪問點(diǎn)。這就提出了一個(gè)問題:如何繞過常規(guī)的構(gòu)造器,,提供一種機(jī)制來保證一個(gè)類只有一個(gè)實(shí)例?客戶程序在調(diào)用某一個(gè)類時(shí),它是不會(huì)考慮這個(gè)類是否只能有一個(gè)實(shí)例等問題的,,所以,,這應(yīng)該是類設(shè)計(jì)者的責(zé)任,,而不是類使用者的責(zé)任。 從另一個(gè)角度來說,,Sigleton模式其實(shí)也是一種職責(zé)型模式,。因?yàn)槲覀儎?chuàng)建了一個(gè)對象,這個(gè)對象扮演了獨(dú)一無二的角色,,在這個(gè)單獨(dú)的對象實(shí)例中,,它集中了它所屬類的所有權(quán)力,同時(shí)它也肩負(fù)了行使這種權(quán)力的職責(zé),! 意圖 保證一個(gè)類僅有一個(gè)實(shí)例,,并提供一個(gè)訪問它的全局訪問點(diǎn)。 模型圖 邏輯模型圖: 物理模型圖: 生活中的例子 美國總統(tǒng)的職位是Sigleton,,美國憲法規(guī)定了總統(tǒng)的選舉,,任期以及繼任的順序。這樣,,在任何時(shí)刻只能由一個(gè)現(xiàn)任的總統(tǒng),。無論現(xiàn)任總統(tǒng)的身份為何,其頭銜"美利堅(jiān)合眾國總統(tǒng)"是訪問這個(gè)職位的人的一個(gè)全局的訪問點(diǎn),。 五種實(shí)現(xiàn) 1.簡單實(shí)現(xiàn)
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} 這種方式的實(shí)現(xiàn)對于線程來說并不是安全的,,因?yàn)樵诙嗑€程的環(huán)境下有可能得到Sigleton類的多個(gè)實(shí)例。如果同時(shí)有兩個(gè)線程去判斷(instance == null),,并且得到的結(jié)果為真,,這時(shí)兩個(gè)線程都會(huì)創(chuàng)建類Sigleton的實(shí)例,這樣就違背了Sigleton模式的原則,。實(shí)際上在上述代碼中,,有可能在計(jì)算出表達(dá)式的值之前,對象實(shí)例已經(jīng)被創(chuàng)建,,但是內(nèi)存模型并不能保證對象實(shí)例在第二個(gè)線程創(chuàng)建之前被發(fā)現(xiàn),。 該實(shí)現(xiàn)方式主要有兩個(gè)優(yōu)點(diǎn): l 由于實(shí)例是在 Instance 屬性方法內(nèi)部創(chuàng)建的,,因此類可以使用附加功能(例如,對子類進(jìn)行實(shí)例化),,即使它可能引入不想要的依賴性,。 l 直到對象要求產(chǎn)生一個(gè)實(shí)例才執(zhí)行實(shí)例化;這種方法稱為“惰性實(shí)例化”,。惰性實(shí)例化避免了在應(yīng)用程序啟動(dòng)時(shí)實(shí)例化不必要的 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 這種方式的實(shí)現(xiàn)對于線程來說是安全的。我們首先創(chuàng)建了一個(gè)進(jìn)程輔助對象,,線程在進(jìn)入時(shí)先對輔助對象加鎖然后再檢測對象是否被創(chuàng)建,,這樣可以確保只有一個(gè)實(shí)例被創(chuàng)建,因?yàn)樵谕粋€(gè)時(shí)刻加了鎖的那部分程序只有一個(gè)線程可以進(jìn)入,。這種情況下,,對象實(shí)例由最先進(jìn)入的那個(gè)線程創(chuàng)建,后來的線程在進(jìn)入時(shí)(instence == null)為假,,不會(huì)再去創(chuàng)建對象實(shí)例了。但是這種實(shí)現(xiàn)方式增加了額外的開銷,,損失了性能,。 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 這種實(shí)現(xiàn)方式對多線程來說是安全的,同時(shí)線程不是每次都加鎖,,只有判斷對象實(shí)例沒有被創(chuàng)建時(shí)它才加鎖,,有了我們上面第一部分的里面的分析,我們知道,,加鎖后還得再進(jìn)行對象是否已被創(chuàng)建的判斷,。它解決了線程并發(fā)問題,同時(shí)避免在每個(gè) Instance 屬性方法的調(diào)用中都出現(xiàn)獨(dú)占鎖定,。它還允許您將實(shí)例化延遲到第一次訪問對象時(shí)發(fā)生,。實(shí)際上,應(yīng)用程序很少需要這種類型的實(shí)現(xiàn),。大多數(shù)情況下我們會(huì)用靜態(tài)初始化,。這種方式仍然有很多缺點(diǎn):無法實(shí)現(xiàn)延遲初始化。 4.靜態(tài)初始化 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 看到上面這段富有戲劇性的代碼,,我們可能會(huì)產(chǎn)生懷疑,,這還是Sigleton模式嗎?在此實(shí)現(xiàn)中,,將在第一次引用類的任何成員時(shí)創(chuàng)建實(shí)例,。公共語言運(yùn)行庫負(fù)責(zé)處理變量初始化。該類標(biāo)記為 sealed 以阻止發(fā)生派生,,而派生可能會(huì)增加實(shí)例,。此外,,變量標(biāo)記為 readonly,這意味著只能在靜態(tài)初始化期間(此處顯示的示例)或在類構(gòu)造函數(shù)中分配變量,。 該實(shí)現(xiàn)與前面的示例類似,,不同之處在于它依賴公共語言運(yùn)行庫來初始化變量。它仍然可以用來解決 Singleton 模式試圖解決的兩個(gè)基本問題:全局訪問和實(shí)例化控制,。公共靜態(tài)屬性為訪問實(shí)例提供了一個(gè)全局訪問點(diǎn),。此外,由于構(gòu)造函數(shù)是私有的,,因此不能在類本身以外實(shí)例化 Singleton 類,;因此,變量引用的是可以在系統(tǒng)中存在的唯一的實(shí)例,。 由于 Singleton 實(shí)例被私有靜態(tài)成員變量引用,,因此在類首次被對 Instance 屬性的調(diào)用所引用之前,不會(huì)發(fā)生實(shí)例化,。 這種方法唯一的潛在缺點(diǎn)是,,您對實(shí)例化機(jī)制的控制權(quán)較少。在 Design Patterns 形式中,,您能夠在實(shí)例化之前使用非默認(rèn)的構(gòu)造函數(shù)或執(zhí)行其他任務(wù),。由于在此解決方案中由 .NET Framework 負(fù)責(zé)執(zhí)行初始化,因此您沒有這些選項(xiàng),。在大多數(shù)情況下,,靜態(tài)初始化是在 .NET 中實(shí)現(xiàn) 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類的一個(gè)靜態(tài)成員來完成,,這樣就實(shí)現(xiàn)了延遲初始化,并具有很多的優(yōu)勢,,是值得推薦的一種實(shí) 實(shí)現(xiàn)要點(diǎn) l Sigleton模式是限制而不是改進(jìn)類的創(chuàng)建。
l Sigleton類中的實(shí)例構(gòu)造器可以設(shè)置為Protected以允許子類派生,。 l Sigleton模式一般不要支持Icloneable接口,,因?yàn)檫@可能導(dǎo)致多個(gè)對象實(shí)例,與Sigleton模式的初衷違背,。 l Sigleton模式一般不要支持序列化,,這也有可能導(dǎo)致多個(gè)對象實(shí)例,這也與Sigleton模式的初衷違背,。 l Sigleton只考慮了對象創(chuàng)建的管理,,沒有考慮到銷毀的管理,就支持垃圾回收的平臺和對象的開銷來講,,我們一般沒必要對其銷毀進(jìn)行特殊的管理,。 l 理解和擴(kuò)展Sigleton模式的核心是“如何控制用戶使用new對一個(gè)類的構(gòu)造器的任意調(diào)用”,。 l 可以很簡單的修改一個(gè)Sigleton,使它有少數(shù)幾個(gè)實(shí)例,,這樣做是允許的而且是有意義的,。 優(yōu)點(diǎn) l 實(shí)例控制:Singleton 會(huì)阻止其他對象實(shí)例化其自己的 Singleton 對象的副本,從而確保所有對象都訪問唯一實(shí)例 l 靈活性:因?yàn)轭惪刂屏藢?shí)例化過程,,所以類可以更加靈活修改實(shí)例化過程 缺點(diǎn) l 開銷:雖然數(shù)量很少,,但如果每次對象請求引用時(shí)都要檢查是否存在類的實(shí)例,將仍然需要一些開銷,??梢酝ㄟ^使用靜態(tài)初始化解決此問題,上面的五種實(shí)現(xiàn)方式中已經(jīng)說過了,。 l 可能的開發(fā)混淆:使用 singleton 對象(尤其在類庫中定義的對象)時(shí),,開發(fā)人員必須記住自己不能使用 new 關(guān)鍵字實(shí)例化對象。因?yàn)榭赡軣o法訪問庫源代碼,,因此應(yīng)用程序開發(fā)人員可能會(huì)意外發(fā)現(xiàn)自己無法直接實(shí)例化此類,。 l 對象的生存期:Singleton 不能解決刪除單個(gè)對象的問題。在提供內(nèi)存管理的語言中(例如基于 .NET Framework 的語言),,只有 Singleton 類能夠?qū)е聦?shí)例被取消分配,,因?yàn)樗瑢υ搶?shí)例的私有引用。在某些語言中(如 C++),,其他類可以刪除 適用性 l 當(dāng)類只能有一個(gè)實(shí)例而且客戶可以從一個(gè)眾所周知的訪問點(diǎn)訪問它時(shí),。 l 當(dāng)這個(gè)唯一實(shí)例應(yīng)該是通過子類化可擴(kuò)展的,并且客戶應(yīng)該無需更改代碼就能使用一個(gè)擴(kuò)展的實(shí)例時(shí),。 應(yīng)用場景 l 每臺計(jì)算機(jī)可以有若干個(gè)打印機(jī),,但只能有一個(gè)Printer Spooler,避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī),。 l PC機(jī)中可能有幾個(gè)串口,,但只能有一個(gè)COM1口的實(shí)例。 l 系統(tǒng)中只能有一個(gè)窗口管理器,。 l .NET Remoting中服務(wù)器激活對象中的Sigleton對象,,確保所有的客戶程序的請求都只有一個(gè)實(shí)例來處理。 完整示例 這是一個(gè)簡單的計(jì)數(shù)器例子,,四個(gè)線程同時(shí)進(jìn)行計(jì)數(shù),。 1using System;
2using System.Threading; 3 4namespace SigletonPattern.SigletonCounter 5{ 6 /**//// <summary> 7 /// 功能:簡單計(jì)數(shù)器的單件模式 8 /// 編寫:Terrylee 9 /// 日期:2005年12月06日 10 /// </summary> 11 public class CountSigleton 12 { 13 /**////存儲唯一的實(shí)例 14 static CountSigleton uniCounter = new CountSigleton(); 15 16 /**////存儲計(jì)數(shù)值 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 /**////計(jì)數(shù)加1 35 public void Add() 36 { 37 totNum ++; 38 } 39 40 /**////獲得當(dāng)前計(jì)數(shù)值 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 /// 功能:創(chuàng)建一個(gè)多線程計(jì)數(shù)的類 9 /// 編寫:Terrylee 10 /// 日期:2005年12月06日 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 /**////構(gòu)造顯示字符串 25 string results = ""; 26 27 /**////創(chuàng)建一個(gè)Sigleton實(shí)例 28 CountSigleton MyCounter = CountSigleton.Instance(); 29 30 /**////循環(huán)調(diào)用四次 31 for(int i=1;i<5;i++) 32 { 33 /**////開始計(jì)數(shù) 34 MyCounter.Add(); 35 36 results +="線程"; 37 results += Thread.CurrentThread.Name.ToString() + "——〉"; 38 results += "當(dāng)前的計(jì)數(shù):"; 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也只執(zhí)行和其他線程相同的工作 75 DoSomeWork(); 76 } 77 } 78} 79
1using System; 2using System.Text; 3using System.Threading; 4 5namespace SigletonPattern.SigletonCounter 6{ 7 /**//// <summary> 8 /// 功能:實(shí)現(xiàn)多線程計(jì)數(shù)器的客戶端 9 /// 編寫:Terrylee 10 /// 日期:2005年12月06日 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 總結(jié) Sigleton設(shè)計(jì)模式是一個(gè)非常有用的機(jī)制,可用于在面向?qū)ο蟮膽?yīng)用程序中提供單個(gè)訪問點(diǎn),。文中通過五種實(shí)現(xiàn)方式的比較和一個(gè)完整的示例,,完成了對Sigleton模式的一個(gè)總結(jié)和探索,。用一句廣告詞來概括Sigleton模式就是“簡約而不簡單”。 _________________________________________________________________________________________________ 參考文獻(xiàn): 《C#計(jì)模式》,,中國電力出版社 使用 Microsoft .NET 的企業(yè)解決方案模式 《Implementing the Singleton Pattern in C#》 MSDN《Exploring the Singleton Design Pattern》 |
|