單例模式概述 定義:確保一個類只有一個實例,並提供一個全局訪問點來訪問這個實例 簡單的說,就是你有且只有一個女朋友(有多個女朋友的模式不是這裡~~),並且你的女朋友很特殊,從來只聽你的話,所以別人想和她交流(訪問她)就必須通過你(全局訪問點)來和她交流。 系統中用到單例模式的地方很多,比如Windo ...
- 單例模式概述
定義:確保一個類只有一個實例,並提供一個全局訪問點來訪問這個實例
簡單的說,就是你有且只有一個女朋友(有多個女朋友的模式不是這裡~~),並且你的女朋友很特殊,從來只聽你的話,所以別人想和她交流(訪問她)就必須通過你(全局訪問點)來和她交流。
系統中用到單例模式的地方很多,比如Windows系統點擊開始只能出現一個開始界面,Ctrl+Alt+. 只能出現一個資源管理器,每個進程有且對應唯一一個進程ID等等。單例模式是為了讓資源得到最大化利用,不浪費資源。同時假如不採用此模式,就可能在不同時刻打開同一個界面,但界面中的內容又各不相同,因而用戶極易產生誤解,影響使用效率。因此單例模式在系統中的應用非常重要。
要點:a.某一個單例類只能有一個實例;
b.必須自行創建這個實例;
c.必須向系統提供這個實例;
- 單例模式的結構與實現
結構:
- 我們考慮一個問題,每一個類都會有它的預設構造函數,或者我們重寫一個構造函數,這個函數都是在創建這個類的實例的時候自動調用的。即對對象進行初始化操作。所以我們每次new的時候都會有一個新的對象,當然這是不符合單例模式的要求的。因此為了滿足單例模式的要求,就必須對類中的函數進行修改,怎麼修改呢,一步一步來(個人覺得這個理解還是挺重要的)。
- 首先,我們類的實例化不能在外部進行(單例類自行提供這個實例),即不能每次new都調用構造函數,因此將構造函數設為private類型
- 既然構造函數是private的,那怎麼樣調用呢??通過公有方法可以調用private函數,返回實例
- 看到這裡,或許有人就有疑問了,公有方法(public ... ....)是在類的對象生成後才可以調用的,但是對象的創建又必須通過構造函數,而這裡構造函數又必須通過公有方法調用,不就形成了一個雞生蛋,蛋孵雞的問題了嗎??好了,誰先誰後,我們先辯論下吧.....其實,靜態方法的使用,能很好的解決這個問題,將公有方法(函數)設為靜態方法,我們就能通過類名.方法名去調用它,繼而調用私有構造函數,產生對象。
- 再有,單例模式創建的女朋友只能是一個,那又怎麼確定呢?怎麼確定你的女朋友是唯一的而且你沒有偷換呢?(~~皮一下),我們就要為這個單例類添加一個變數了,用來確認是否唯一,由於創建的整個入口是靜態公有方法,所以在那時就要判斷是否唯一了,如果該單例類有了一個女朋友,判斷後便不再創建,如果當前沒有,則分配一個。而靜態成員函數可以直接訪問類的靜態數據和函數成員,而訪問非靜態成員,必須通過對象名,這有陷入雞和蛋的問題里了,所以,該變數設為靜態變數就很方便了,它也滿足為整個類服務的特性。
- 總結下步驟 :(1)私有構造函數;(2)公有方法調用私有函數;(3)設為靜態方法;(4)添加靜態變數;(5)根據變數數判斷是否生成女朋友(唯一的對象);
- 上圖中:↓↓↓↓↓
- Singleton(單例),在單例類的內部創建它的唯一實例
- Getinstance(靜態方法),通過它產生唯一實例
- instance(靜態變數),判斷是否可以產生實例
實現:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Singleton 7 { 8 class Singleton 9 { 10 private static Singleton instance = null;//靜態私有成員變數 11 12 //私有構造函數 13 private Singleton() 14 { 15 Console.WriteLine("恭喜你,獲得一個女朋友~~"); 16 } 17 18 //靜態公有方法,返回實例 19 public static Singleton Getinstance() 20 { 21 if(instance == null)//沒女朋友 22 instance = new Singleton();//生成一個吧 23 return instance;//有就返回當前的, 24 } 25 } 26 class Program 27 { 28 static void Main(string[] args) 29 { 30 Singleton s1 = Singleton.Getinstance(); 31 Singleton s2 = Singleton.Getinstance(); 32 if (s1 == s2) 33 { 34 Console.WriteLine("怎麼能想要共有一個女朋友呢?S2 趕緊換一個吧..."); 35 } 36 } 37 } 38 }
結果:
- 餓漢式單例和懶漢式單例
剛看到這兩個單例的名字時還是有點好笑的,如此這麼生動形象呢,就好像餓漢式單身(連溫飽都滿足不了,何來女朋友呢),懶漢式(好吃懶做的,也很難...)不亂扯了,回主題。餓漢式單例正如餓漢一樣,很餓很餓的人最想要的就是立即馬上吃東西。因此餓漢式單例在定義靜態變數時就實例化了單例類,因為實在太餓了啊,等不及了。
1 class EagetSingleton 2 { 3 private static EagetSingleton instance = new EagetSingleton();//靜態變數實例化單例類 4 5 private EagetSingleton(){} 6 7 public static EagetSingleton GetInstance() 8 { 9 return instance; 10 } 11 }
懶漢式單例類則是在類第一次被引用時將自己實例化,單例類被載入時不會實例化,所以這很符合懶漢的氣質~但是在這裡要註意的是,在定義靜態變數時沒有實例化單例類,而是在第一次調用靜態方法時實例化單例類,這就會產生問題,高併發,多線程實現懶漢式單例時會創建多個對象,從而違背了單例模式的設計意圖。也就是還是要對女朋友的個數進行判斷。這要怎麼辦呢?在多線程的情況下,就要對該代碼段進行控制,即每次只讓一個線程進入並創建實例,也就是相當於現在的“共用女友”,幫你拍照啊,陪你去看電影啊 ,巴拉巴拉。但是,該“共用女友”有且只有一個,即單例類的唯一實例。所以,土豪們(各個線程)得一個一個租用,上一個用完了下一個才能租用。因此代碼如下:
1 class LazytSingleton 2 { 3 private static LazytSingleton instance = null; 4 private static readonly object synRoot = new object();//看做一個門。 5 //程式運行時創建只讀輔助對象 6 7 private LazytSingleton(){} 8 9 public static LazytSingleton GetInstance() 10 { 11 if(instance == null)//在房間外問:房間里有人嗎 ? 沒人回應 ,可能沒,可能下一秒有人進去 ,我卻以為沒人 12 { 13 lock(synRoot)//第二次判斷 //把門關了,外麵線程進不來,只能裡面的出來,外面的才能進 14 { 15 if(instance == null)//繼續問,房間里有人嗎? 有就真的有,沒有就真的沒 16 { 17 instance = new LazytSingleton();//創建實例 18 } 19 } 20 } 21 } 22 }
二者比較:
- 餓漢式單例
優點:無需考慮多線程同時訪問的問題,確保實例唯一性。調用速度和反應時間快於懶漢模式,因為餓漢一開始就創建,後面則直接拿來用就可以了。
缺點:不管單例對象是否需要,都會在類載入時創建,這樣不如懶漢式單例,資源利用不高,且載入時間較長。如啟動VS,Eclipse等,需要loading許多可能要的可能不要的,要等啊...
- 懶漢式單例
優點:第一次使用時創建,不會一直占用資源,即延遲載入。
缺點:必須考慮多線程問題,特別是單例類作為資源控制器時,會涉及資源初始化,也會耗費許多時間,也會出現多線程同時首次引用此類,造成擁堵,導致系能性能降低
- 單例模式的優缺點和適用環境
- 單例模式的優點
- 提供唯一實例的受控訪問,可以嚴格控制何時訪問
- 由於只存在一個對象,可以節省系統資源
- 如果將單例模式的實例數目變為可變,即將單例模式進行擴展,即可獲得指定數目的實例對象,即節省資源,又提高效率(相當於多例類)
- 單例模式的缺點
- 沒有抽象層,擴展有較大困難
- 單例類職責過重,一定程度上違背了單一職責原則(單例類即提供業務方法,又提供了創建對象的方法(工廠方法),對象創建和對象本身耦合在了一起)
- C#、JAVA 擁有GC(自動垃圾回收機制) (可以去瞭解下) ,在實例化的對象長時間不被利用,會被誤認為垃圾進行自動銷毀,下次利用又要重新創建,導致共用的單例對象丟失(女朋友丟了可不好受啊..)
- 單例模式的適用環境
- 在系統只要一個實例對象(man只要一個girlfriend)/(資源管理器).....
- 客戶調用類的單個實例只允許使用一個公共訪問點,除了該點外,不允許其他途徑來訪問實例