Singleton 是非常經典的 Design Pattern。最簡單,也是最具實用性的 pattern。
我也最常拿來考面試者。一個有經驗的 programmer,怎麼可能沒碰過 Singleton?
目的
全域只有一個實例(instance)
舉例
一家銀行的分行,通常會有多個櫃台(窗口),以加快處理速度。但只能有一個取號機,避免號碼牌重覆。
因此,該取號機只能有一台。如果有兩台,天下就大亂了。
程式在實作時,也常常有類似的需求。如何才能確保一個 instance呢?大家經驗累積的結果,都有類似的樣式(pattern)。這樣的 pattern 後來被取名為 singleton,以方便大家的溝通。
策略
- 將預設建構子封閉起來,不讓外界直接建立 instance.
- 使用靜態屬性(static property),讓外界取的唯一的 instance
最常見的實作如下。
實作1
class MySingleton
{
private static MySingleton _singleton = null;
private int currentNumber = 0;
private MySingleton()
{
}
public static MySingleton Current
{
get
{
if (_singleton == null)//注意這一行
_singleton = new MySingleton();
return _singleton;
}
}
public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}
Client
而Client code 的使用方式如下
MySingleton.Current.WriteNumber();
MySingleton.Current.WriteNumber();
MySingleton.Current.WriteNumber();
呼叫的結果,就是 1, 2, 3了。
實作1的方法並不完美,因為不是 thread-safe。換句話說,使用在 multi-thread 的環境下,是有可能還是建立了多個 instance。可能發生問題的程式碼,就是實作1內註解的「注意這一行」。
因此,有人改寫如下
實作2: thread-safe
class MySingleton
{
private static MySingleton _singleton = null;
private int currentNumber = 0;
private MySingleton()
{
}
public static MySingleton Current
{
get
{
if (_singleton == null)
{
lock(typeof(MySingleton))
_singleton = new MySingleton();
}
return _singleton;
}
}
public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}
實作2的方法,是使用了 lock 的 c# 語法,讓進入 lock區段的執行緒只能有一個。
實作2其實已經相當完美了。唯一美中不足的是,簡單的singleton,竟然要這麼複雜?因此,又有人改寫如下
實作3: thread-safe, eager singleton
class MySingleton
{
private static MySingleton _singleton = new MySingleton();
private int currentNumber = 0;
private MySingleton()
{
}
public static MySingleton Current
{
get
{
return _singleton;
}
}
public void WriteNumber()
{
currentNumber++;
Console.WriteLine(currentNumber);
}
}
實作3是使用了 static 的 instance,並在一開始就建立了 instance。即使用了private static MySingleton _singleton = new MySingleton();這一行。
這樣的好處,是大幅減少了程式碼的複雜度,相當容易維護。我相當推薦這一型。
實作3有一個壞處,當 Client 尚未需要 singleton的 instance時,程式就先建立了 instance。而實作2時會先判斷是否尚未建立實例。
if (_singleton == null)
因此,實作2是lazy singleton「要用時才建立instance」, 但not thread-safe。而實作3是eager singleton,「未用時已建立 instance」, thread-safe。
有沒有既是 thread-safe, 又是 lazy singleton 的寫法呢?因此產生 實作4
實作4: thread-safe, lazy singleton
class MySingleton
{
private int currentNumber = 0;
private MySingleton() { }
public static MySingleton Current
{
get {
return Inner.Current;
}
}
public void WriteNumber() {
currentNumber++;
Console.WriteLine(currentNumber);
}
class Inner {
static Inner() { }
internal static readonly MySingleton Current = new MySingleton();
}
}
實作4非常巧妙地使用 inner class 並結合 static instance 的用法,完美地完成了任務,是我目前看過最棒的 singleton 解法。