開始前先聲明(坦白從寬~),我對Design Pattern的研究十分淺薄,寫起IoC、Singleton的題材有種越級打怪的心虛感,我知道本部落格有不少讀者深諳此道,如筆記有疑或有誤之處,懇請十方大德不吝指正。
Singleton是挺常見的設計模式,旨在確保該型別於Process中只會產生單一Instance(執行個體)。在.NET實現Singleton慣用的做法是將建構式設成private,另外宣告一個static屬性命名為Instance,在第一次get時建立物件,之後每次要取用該類別時不再重新建構,而是直接取用Instance屬性,如下例:
using System;
using System.Threading;
publicclass TheOne
{
private Guid UniqueKey = Guid.NewGuid();
privatestatic TheOne instance = null;
publicstatic TheOne Instance
{
get
{
if (instance == null)
{
instance = new TheOne();
}
return instance;
}
}
/// <summary>
/// 建構式
/// </summary>
private TheOne()
{
Thread.Sleep(2000);
Console.WriteLine("Constructor Executed");
}
publicvoid ShowUniqueKey()
{
Console.WriteLine("Unique Key={0}", UniqueKey);
}
}
應用時透過TheOne.Instance取得唯一的執行個體:
staticvoid 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"(誤):
staticvoid 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)機制並改寫如下:
privatestatic TheOne instance = null;
privatestaticobject syncRoot = new Object();
publicstatic 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;
publicclass TheNewOne
{
private Guid UniqueKey = Guid.NewGuid();
public TheNewOne()
{
Thread.Sleep(2000);
Console.WriteLine("Constructor Executed");
}
publicvoid ShowUniqueKey()
{
Console.WriteLine("Unique Key={0}", UniqueKey);
}
}
測試時先宣告ContainerBuilder,註冊TheNewOne型別並宣告SingleInstane(),後續使用時只需透過ResolveType<TheNewOne>()取得TheNewOne,就是Singleton了。
staticvoid 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直接存取類別可視為"違法亂紀"的罪行,視為人員管理問題而非架構缺陷。依此前題,建構式公開就不算嚴重缺失,可安心服用。