設計模式之單例模式

設計模式之單例模式

Intro

一個類只容許建立惟一一個對象(或者實例),那這個類就是一個單例類,這種設計模式就叫做單例設計模式,簡稱單例模式。git

單例模式多是你們據說最多的設計模式了,網上介紹最多的設計模式大概就是單例模式了,我看過的設計模式相關的文章不少都是寫一篇介紹單例模式,而後就沒有了。程序員

經典的設計模式有 23 種, 若是隨便抓一個程序員,讓他說一說最熟悉的 3 種設計模式,那其中確定會包含今天要講的單例模式,github

使用場景

單例模式主要用來確保某個類型的實例只能有一個。好比手機上的藍牙之類的只能有一個的實例的場景能夠考慮用單例模式。設計模式

主要做用:安全

  • 處理資源訪問衝突,好比說上面說的系統惟一硬件,系統文件訪問衝突等
  • 表示全局惟一類,好比系統中的惟一 id 生成器

單例模式的實現

單例模式的實現,一般須要私有化構造方法,防止外部類直接使用單例類的構造方法建立對象多線程

簡單非線程安全的實現

public class Singleton
{
    private static Singleton _instance;

    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Singleton();
        }

        return _instance;
    }
}

這種方式比較簡單,可是不是線程安全的,多線程高併發狀況下可能會致使建立多個實例,可是若是你的業務場景容許建立多個,我以爲問題也不大,若是必定要保證只能建立一個實例,能夠參考下面的作法架構

雙檢鎖(懶漢式)

/// <summary>
/// 雙重判空加鎖,飽漢模式(懶漢式),用到的時候再去實例化
/// </summary>
public class Singleton
{
    private static volatile Singleton _instance;
    private static readonly object SyncLock = new object();

    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            lock (SyncLock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }

        return _instance;
    }
}

這種方式的執行過程會先檢查是否完成了實例化,若是已經實例化則直接返回實例,若是沒有就嘗試獲取鎖,得到鎖以後再判斷一下是否已經實例化,若是已經實例化則返回實例,若是沒有就進行實例化併發

靜態初始化(餓漢式)

/// <summary>
/// 餓漢模式-就是屌絲,擔憂餓死。類加載就給準備好
/// </summary>
public sealed class Singleton1
{
    /// <summary>
    /// 靜態初始化,由 CLR 去建立,無需加鎖
    /// </summary>
    private static readonly Singleton1 Instance = new Singleton1();

    private Singleton1()
    {
    }

    public static Singleton1 GetInstance() => Instance;
}

這也是一種常見的實現單例模式的用法,可是這種方式就不支持懶加載了,不像上面那種方式能夠作到須要的時候再實例化,適用於這個對象會被頻繁使用或者這個類比較小,是否實例化沒有什麼影響。框架

併發字典型

這個是以前忘記在哪裏看到的微軟框架裏的一段代碼,相似,可能和源碼並不徹底同樣,只是提供一種實現思路asp.net

/// <summary>
/// 使用 ConcurrentDictionary 實現的單例方法,用到的時候再去實例化
/// 這種方式相似於第一種方式,只是使用了併發集合代替了雙重判斷和 lock
/// </summary>
public class Singleton2
{
    private static readonly ConcurrentDictionary<int, Singleton2> Instances = new ConcurrentDictionary<int, Singleton2>();

    private Singleton2()
    {
    }

    public static Singleton2 GetInstance() => Instances.GetOrAdd(1, k => new Singleton2());
}

Lazy

C# 裏提供了 Lazy 的方式實現延遲實例化

/// <summary>
/// 使用 Lazy 實現的單例方法,用到的時候再去實例化
/// </summary>
public class Singleton3
{
    private static readonly Lazy<Singleton3>
        LazyInstance = new Lazy<Singleton3>
        (() => new Singleton3());

    private Singleton3()
    {
    }

    public static Singleton3 GetInstance() => LazyInstance.Value;
}

其餘

你也可使用內部類等實現方式,這裏就不介紹了,想了解能夠本身網上找一下

驗證是否線程安全,驗證示例代碼:

Console.WriteLine($"Singleton");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton1");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton1.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton2");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton2.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton3");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton3.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

上面的 WhenAll 是一個擴展方法,就是調用的 Task.WhenAll,輸出示例:

單例模式的存在的問題

  • 單例對 OOP 特性的支持不友好,使用單例模式一般也就意味着放棄了 OOP 的繼承,多態特性
  • 單例會隱藏類之間的依賴關係,單例模式,不容許顯示 new,使得對象的建立過程對外部來講是不可見的,內部有哪些依賴對外也是不可見的,這樣在系統重構的時候就會很危險,很容易形成系統出現問題
  • 單例對代碼的擴展性不友好,單例類只能有一個對象實例。若是將來某一天,咱們須要在代碼中建立兩個實例或多個實例,那就要對代碼有比較大的改動
  • 單例對代碼的可測試性不友好,若是單例類依賴比較重的外部資源,好比 DB,咱們在寫單元測試的時候,但願能經過 mock 的方式將它替換掉。而單例類這種硬編碼式的使用方式,致使沒法實現 mock 替換
  • 單例不支持有參數的構造函數,單例模式一般使用私有構造方法,並且只會調用一次構造方法,因此一般不支持構造方法參數,若是有參數一般會給調用方形成誤解,兩次調用傳遞的參數不一致的時候如何處理是一個問題

More

隨着如今依賴注入思想的普及,asp.net core 更是基於依賴框架構建的,使用依賴注入的方式能夠較好的解決上面的各類問題

基於依賴注入框架,你能夠沒必要擔憂對象的建立和銷燬,讓依賴注入框架管理對象,這樣這個要實現單例模式的類型能夠和其餘普通類型同樣,只須要使用依賴注入框架註冊服務的時候指定服務生命週期爲單例便可,好比使用微軟的依賴注入框架的時候可使用 services.AddSingleton<TSingletonService>(); 來註冊單例服務

Reference

相關文章
相關標籤/搜索