一、前言
在上一節中我們對設計模式進行了一定的介紹及分類。設計模式分為創建型、結構型、行為型。創建型模式——主要負責對象的創建。結構型職責——主要負責處理類與對象的組合。行為型模式——主要負責類與對象交互中的職責的分配問題。今天我們也是講述介紹創建型模式中的第一個模式——單例模式。
二、單例模式介紹
(一)來由
單例模式(Singleton Pattern)是最簡單的一個設計模式 ,這種設計模式屬於創建型模式。在程序中總會有一些特殊的類。它們必須保證在系統中只存在一個實例,這個單一的類自己創建自己的對象,同時確保只有單個對象被創建,並且提供唯一的訪問形式。可以直接進行訪問,不用再新建實例。
那么如何避開常規的設計,來實現一個類一個實例、並且保證唯一調用呢?這時候就是單例模式施展身手的時候了。
(二)意圖
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
(三)單例模式實現方法
單例模式到底又是如何實現的呢?既然是單一實例,那么隊友多線程又該如何處理呢?下面我們一一來看看單例模式的實現。單例模式我們又涉及到其實現的多種形式——非線程安全、線程安全、雙重驗證線程安全、不用鎖線程安全、完全延遲加載、使用.NET4的Lazy<T>類型。
1. 非線程安全

/// <summary>
/// 非線程安全 /// </summary>
public sealed class Singleton1 { /// <summary>
/// 定義靜態變量保存實例 /// </summary>
public static Singleton1 Instance = null; /// <summary>
/// 定義私有構造函數保護,使其他地方不得實例 /// </summary>
private Singleton1() { } public string GetString() { return "非線程安全的單例模式"; } /// <summary>
/// 定義公共方法,實現全局訪問 /// </summary>
/// <returns></returns>
public static Singleton1 GetInstance() { //判斷實例狀態
if (Instance==null) { Instance = new Singleton1(); } return Instance; } }
在上述事例中完美的實現了單線程的單例模式的情況。這里我們也需要注意一些的情況:
① 單例類包含一個private的私有構造函數
② 類申明sealed 密封不可繼承(不強制)
③ 類中有一個靜態變量保存實例
④ 類中提供有一個靜態方法或者屬性實現實例的創建引用全局調用訪問
⑤ 在多線程中單例模式需要另行處理,不然有可能得到類的多個實例
2. 線程安全

/// <summary>
/// 線程安全單例模式 /// </summary>
public sealed class Singleton2 { /// <summary>
/// 定義靜態變量保存實例 /// </summary>
public static Singleton2 Instance = null; private static readonly object locks=new object(); /// <summary>
/// 定義私有構造函數保護,使其他地方不得實例 /// </summary>
private Singleton2() { } public string GetString() { return "線程安全的單例模式"; } /// <summary>
/// 定義公共方法,實現全局訪問 /// </summary>
/// <returns></returns>
public static Singleton2 GetInstance() { //對線程進行加鎖限制,掛起后來的線程。保證實例安全
lock (locks) { if (Instance == null) { Instance = new Singleton2(); } } return Instance; } }
3. 不用鎖線程安全

/// <summary>
/// 不用鎖線程安全單例模式 /// </summary>
public sealed class Singleton3 { /// <summary>
/// 定義靜態變量保存實例 /// </summary>
private static readonly Singleton3 Instance = new Singleton3 (); static Singleton3() { } /// <summary>
/// 定義私有構造函數保護,使其他地方不得實例 /// </summary>
private Singleton3() { } public string GetString() { return "不用鎖線程安全單例模式"; } /// <summary>
/// 定義公共方法,實現全局訪問 /// </summary>
/// <returns></returns>
public static Singleton3 GetInstance() { return Instance; } }
這個實現方法沒有使用到鎖,但是也實現了線程安全。在第一次調用的時候會創建一個instance。這個實現也有一定的安全隱患。
-
-
- instance被創建的時機不明,任何對Singleton的調用都會提前創建instance
- static構造函數的循環調用。如有A,B兩個類,A的靜態構造函數中調用了B,而B的靜態構造函數中又調用了A,這兩個就會形成一個循環調用,嚴重的會導致程序崩潰。
- 我們需要手動添加Singleton的靜態構造函數來確保Singleton類型不會被自動加上beforefieldinit這個Attribute,以此來確保instance會在第一次調用Singleton時才被創建。
- readonly的屬性無法在運行時改變,如果我們需要在程序運行時dispose這個instance再重新創建一個新的instance,這種實現方法就無法滿足。
-
4. 完全延遲加載

/// <summary>
/// 實現完全延遲加載單例模式 /// </summary>
public sealed class Singleton4 { /// <summary>
/// 定義私有構造函數保護,使其他地方不得實例 /// </summary>
private Singleton4() { } /// <summary>
/// 提供訪問位置 /// </summary>
public static Singleton4 Instance { get { return GetInstance.instance; } } /// <summary>
/// 定義私有類確保第一次加載是初始化及調用 /// </summary>
private class GetInstance { static GetInstance(){} internal static readonly Singleton4 instance = new Singleton4(); } public string GetString() { return "實現完全延遲加載單例模式"; } }
它確保了instance只會在Instance的get方法里面調用,且只會在第一次調用前初始化。是上一個版本的延遲加載的版本
5. 使用.NET4的Lazy<T>類型

/// <summary>
/// 使用Lazy<T>實現完全延遲加載單例模式 /// </summary>
public sealed class Singleton5 { /// <summary>
/// 延遲加載初始化 /// </summary>
private static readonly Lazy<Singleton5> lazy=new Lazy<Singleton5>(()=>new Singleton5()); /// <summary>
/// 定義私有構造函數保護,使其他地方不得實例 /// </summary>
private Singleton5() { } /// <summary>
/// 提供全局訪問點 /// </summary>
/// <returns></returns>
public static Singleton5 Instance() { return lazy.Value; } public string GetString() { return "實現完全延遲加載單例模式"; } }
在.NET4.0中,可以使用Lazy<T> 來實現對象的延遲初始化,從而優化系統的性能。延遲初始化就是將對象的初始化延遲到第一次使用該對象時。延遲初始化是我們在寫程序時經常會遇到的情形,例如創建某一對象時需要花費很大的開銷,而這一對象在系統的運行過程中不一定會用到,這時就可以使用延遲初始化,在第一次使用該對象時再對其進行初始化,如果沒有用到則不需要進行初始化,這樣的話,使用延遲初始化就提高程序的效率,從而使程序占用更少的內存。
三、使用場合及優缺點
一、使用場合
1、當類只需要且只能有一個實例並且需要全局訪問的時候。
2、當類是使用子類化擴展,並且無需更改代碼就可以使用擴展實例的情況下。
二、優點
1、控制實例數量:保證實例數量的唯一且是全局訪問。
2、靈活性:類控制了實例化的全過程,這樣可以更加靈活的修改實例化過程
3、資源節省:避免對資源的多重占用
三、缺點
1、沒有接口、也不能繼承。這個與單一責任原則相沖突,一個類只應該負責其邏輯,而不應該去負責如何實例。
四、總結
在設計模式的學習過程中,單例模式較為簡單,實現操作並不是特別難,但是在我們實例運用中也當注意下,比較如果使用出現問題。找到問題還是稍微困難的。這篇文章也介紹了幾種單例模式的使用方法,在我們使用時擇優選取最佳方案。下一節我們將為全面講述二級野怪、並學習攻克它。
生命不息、戰斗不止!
歡迎大家掃描下方二維碼,和我一起踏上設計模式的闖關之路吧!