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