設計模式-單例模式

煮詩君發表於2020-08-25

定義


單例模式,屬於建立型別的一種常用的設計模式。它的目的就是為了建立的類在當前程式中只有一個例項

目的


從定義可以看出,使用單例模式的目的無非就是下面兩個:

  • 全域性唯一
  • 全域性共享

優點


  • 確保全域性共享同一個例項
  • 節約系統資源

實現手段


1. 靜態類

這種方式不是單例模式,但可以滿足需求,在正式生產中也會經常用到。

程式碼

public static class SingletonSample1  
{  
    private static int _counter = 0;
    
    public static int IncreaseCount()  
    {  
        return ++_counter;  
    }
}

注意:這裡的++_counter其實存在高併發問題,嚴格上應該用Interlocked.Increment(ref _counter)的方式,由於我們主要講的是單例模式並且簡單且能演示效果,所以故意忽略了這一點。下同

優點

  • 使用起來方便,簡單

缺點

  • 靜態類不能繼承類,也不能實現介面,不能通過介面或者抽象方法(虛方法)實現多型;
  • 靜態類必須在第一次載入時初始化,如果專案中用不到會導致資源浪費;

2. 單例模式一

這是最簡單的一種單例模式,也是比較常用的一種方式,可在正式生產中使用。

程式碼

public sealed class SingletonSample2  
{  
    private static readonly SingletonSample2 _instance = new SingletonSample2();

    private int _counter = 0;

    private SingletonSample2() { }

    public static SingletonSample2 Instance
    {
        get
        {
            return _instance;
        }
    }

    public int IncreaseCount()
    {
        return ++_counter;
    }
}

優點

  • 解決了靜態類不能繼承類,不能實現介面,不能通過介面或者抽象方法(虛方法)實現多型的問題;

缺點

  • 沒有解決第一次載入時初始化,資源浪費的問題。

以上兩種方式都存在第一次載入時,資源浪費的問題,但在記憶體資源越來越便宜的今天,通常這種浪費是可以接受的,因此也不必過於糾結這種浪費。當然,在條件允許的情況下,能優化還是要優化的。

3. 單例模式二

該方式是改進過程中的過渡階段,不可用於生產。

程式碼

public class SingletonSample3
{
    private static SingletonSample3 _instance;

    private int _counter = 0;

    private SingletonSample3() { }

    public static SingletonSample3 Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new SingletonSample3();
            }

            return _instance;
        }
    }

    public int IncreaseCount()
    {
        return ++\_counter;
    }
}

優點

  • 解決了資源浪費的問題;

缺點

  • 引入了高併發的新問題。

4. 單例模式三

該方式也是改進過程中的過渡階段,不可用於生產。

程式碼

public class public class SingletonSample4
{
    private static SingletonSample4 _instance;
    private static readonly object _locker = new object();
    private int _counter = 0;

    private SingletonSample4() { }

    public static SingletonSample4 Instance
    {
        get
        {
            lock (_locker)  
            {
                if (_instance == null)
                {
                    _instance = new SingletonSample4();
                }

                return _instance;
            }
        }
    }

    public int IncreaseCount()
    {
        return ++_counter;
    }
}

注意:視訊中講到這裡時,我其中有提到熱啟動關鍵詞,我把系統預熱口誤說成了熱啟動,由於這兩個概念之間有較大的差別,所以這裡糾正一下。

優點

  • 解決了高併發問題;

缺點

  • 引入了效能問題。

5. 單例模式四

著名的雙檢鎖模式,完美解決問題,可用於生產。

程式碼

public class SingletonSample5
{
    private static volatile SingletonSample5 _instance;
    private static readonly object _locker = new object();
    private int _counter = 0;

    private SingletonSample5() { }

    public static SingletonSample5 Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_locker)
                {
                    if (_instance == null)
                    {
                        _instance = new SingletonSample5();
                    }
                }
            }

            return _instance;
        }
    }

    public int IncreaseCount()
    {
        return ++_counter;
    }
}

注意:volatile是必須的,因為它可以保證new不會被指令重排序,詳細可看視訊部分的分析。

優點

  • 解決了上述實現方式的各種設計缺陷;

缺點

  • 程式碼有點複雜。

6. 單例模式五

.Net支援的一種優雅版本的實現方式,前面講了那麼多其實就是為了引出該方式,強烈建議使用該版本

程式碼

public class SingletonSample6
{
    private static readonly Lazy<SingletonSample6> _instance  
        = new Lazy<SingletonSample6>(() => new SingletonSample6());

    private int _counter = 0;

    private SingletonSample6() { }

    public static SingletonSample6 Instance
    {
        get
        {
            return _instance.Value;
        }
    }

    public int IncreaseCount()
    {
        return ++_counter;
    }
}

優點

  • 程式碼優雅簡潔同時滿足需求

缺點

  • 當系統中有大量單例模式時,會有較多重複程式碼

7. 單例模式六

泛型版本,是否使用視情況而定。

程式碼

public class SingletonSampleBase<TSingleton> where TSingleton : class
{
    private static readonly Lazy<TSingleton> _instance  
        = new Lazy<TSingleton>(() => (TSingleton)Activator.CreateInstance(typeof(TSingleton), true));

    protected SingletonSampleBase() { }

    public static TSingleton Instance
    {
        get
        {
            return _instance.Value;
        }
    }
}

public class SingletonSample7 : SingletonSampleBase<SingletonSample7>
{
    private int _counter = 0;

    private SingletonSample7() { }

    public int IncreaseCount()
    {
        return ++_counter;
    }
}

優點

  • 封裝了重複程式碼

缺點

  • 違反了依賴倒置原則(雖然在父類中是通過反射建立的子類,但本質還是在父類中建立了子類)

總結


  • 單例模式還可通過IOC容器實現,視訊中在講到IOC容器是也發生了多次口誤,將註冊說成了注入,這裡也糾正一下。
  • 最後舉的一個用單例模式實現SqlHelper的例子,重點是為了突出相對於靜態類,例項類在多型擴充套件方面的優勢,其實如果沒有類似這種擴充套件需求,靜態類就足以應付絕大多數的需求。

單例模式實現方式如此之多,但實際上大多數情況需要使用單例的時候都可以用靜態類實現,比如一些工具類,而其他場景直接用單例模式五或者單例模式六即可,著名的雙檢索其實也是大可不必的,畢竟跟單例模式五相比,體現不出任何優勢,還更容易出錯。

原始碼連結

相關文章