開始前先聲明(坦白從寬~),我對Design Pattern的研究十分淺薄,寫起IoC、Singleton的題材有種越級打怪的心虛感,我知道本部落格有不少讀者深諳此道,如筆記有疑或有誤之處,懇請十方大德不吝指正。

Singleton是挺常見的設計模式,旨在確保該型別於Process中只會產生單一Instance(執行個體)。在.NET實現Singleton慣用的做法是將建構式設成private,另外宣告一個static屬性命名為Instance,在第一次get時建立物件,之後每次要取用該類別時不再重新建構,而是直接取用Instance屬性,如下例:

using System;
using System.Threading;
 
public class TheOne
{
    private Guid UniqueKey = Guid.NewGuid();
 
    private static TheOne instance = null;
    public static TheOne Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new TheOne();
            }
            return instance;
        }
    }
 
    /// <summary>
    /// 建構式
    /// </summary>
    private TheOne()
    {
        Thread.Sleep(2000);
        Console.WriteLine("Constructor Executed");
    }
 
    public void ShowUniqueKey()
    {
        Console.WriteLine("Unique Key={0}", UniqueKey);
    }
 
}

應用時透過TheOne.Instance取得唯一的執行個體:

        static void Test1()
        {
            for (int i = 0; i < 3; i++)
            {
                TheOne theOne = TheOne.Instance;
                theOne.ShowUniqueKey();
            }
        }

執行後可驗證TheOne只被建構了一次,三次使用的都是同一Instance。

Constructor Executed
Unique Key=571842aa-5037-43db-9341-6e82f0ebe6d0
Unique Key=571842aa-5037-43db-9341-6e82f0ebe6d0
Unique Key=571842aa-5037-43db-9341-6e82f0ebe6d0

不過,老鳥們都知道上述寫法未考慮Thread-Safe,在多執行緒下肯定破功。以下我們就來踢爆這個"黑心Singleton"(誤):

        static void Test2()
        {
            for (int i = 0; i < 3; i++)
            {
                ThreadPool.QueueUserWorkItem((o) =>
                {
                    TheOne theOne = TheOne.Instance;
                    theOne.ShowUniqueKey();
                });
            }
        }

當改用ThreadPool以三個執行緒同時存取TheOne。很好! 建構式跑了三次,生出三個TheOne…

Constructor Executed
Constructor Executed
Unique Key=a3f52f2f-6097-4a90-8a26-5cb91b12c484
Constructor Executed
Unique Key=a06da108-c38b-43c9-aa01-04f5e261c121
Unique Key=37063ff3-990c-44a0-ac0a-798ea0bcf1ae

微軟有一篇很棒的文章詳細討論了.NET Singleton實作,如果要做到Thread-Safe,TheOne最好加上雙重檢查鎖定(Double-Check Locking)機制並改寫如下:

    private static TheOne instance = null;
    private static object syncRoot = new Object();
    public static TheOne Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new TheOne();
                }
            }
            return instance;
        }
    }

如此就能確保多執行緒下也只產生唯一的Instance。

Autofac提供了另一種實現Singleton的選擇,做法是在ContainerBuilder註冊型別時呼叫SngleInstance(),任何類別,不需特殊設計都能實現Singletone。我們另外宣告一個TheNewOne類別,功能與TheOne類似,但直接提供public的建構式,省去static Instace屬性,Singleton需求交給Autofac處理:

using System;
using System.Threading;
 
public class TheNewOne
{
    private Guid UniqueKey = Guid.NewGuid();
 
    public TheNewOne()
    {
        Thread.Sleep(2000);
        Console.WriteLine("Constructor Executed");
    }
 
    public void ShowUniqueKey()
    {
        Console.WriteLine("Unique Key={0}", UniqueKey);
    }
}

測試時先宣告ContainerBuilder,註冊TheNewOne型別並宣告SingleInstane(),後續使用時只需透過ResolveType<TheNewOne>()取得TheNewOne,就是Singleton了。

        static void Test3()
        {
            ContainerBuilder builder = new ContainerBuilder();
            //註冊時加註SingleInstance(),Autofac便會以Singleton方式提供物件
            builder.RegisterType<TheNewOne>().SingleInstance();
            IContainer container = builder.Build();
 
            for (int i = 0; i < 3; i++)
            {
                ThreadPool.QueueUserWorkItem((o) =>
                {
                    TheNewOne theOne = container.Resolve<TheNewOne>();
                    theOne.ShowUniqueKey();
                });
            }
        }

測試結果,類別不用特別加入Singleton邏輯就實現了多執行緒下的Singleton。

Constructor Executed
Unique Key=972def1e-8788-45aa-bd15-2aef15870514
Unique Key=972def1e-8788-45aa-bd15-2aef15870514
Unique Key=972def1e-8788-45aa-bd15-2aef15870514

【結論】透過IoC(Autofac)實現Singleton,相形之下比自己DIY簡便許多,但有個缺點: 由於類別建構式公開,無法禁止開發人員繞過Autofac自行另建Instance。但在實務上,若架構啟用Autofac或任何IoC,多會另外宣告Interface,開發人員依賴的是Interface而非Class本身,對底層究竟使用何Class理應處於"無知"狀態,越過Interface直接存取類別可視為"違法亂紀"的罪行,視為人員管理問題而非架構缺陷。依此前題,建構式公開就不算嚴重缺失,可安心服用。


Comments

Be the first to post a comment

Post a comment