前言
昨天在寫 WPF應用 的時候,遇到了一個 static 的問題。呼叫方要求 function 必須是 static,但偏偏這個 function 又不太好直接 static。
在一通折騰之後,順便從同事那裡學到一個新招: 單例模式。
單例模式
單例模式是一種設計模式,目的是用來確保在一定情況下只有一個實例被建立,並且開放一個全局接口讓外部使用這個實例。如此一來就不用到處在各個地方 new 一個實例出來,不但消耗資源,某些情況下也不好管理。
以下用 C# 做實作。
簡單實作
假設我寫了一個類別(MessageControl),用於控制訊息的各種操作。
public class MessageControl
{
public MessageControl() { }
public void PrintMessage()
{
Console.WriteLine("Hello, I am Singleton");
}
}
一般來說如果要使用這個 class,就必須為其 new 一個實體。然後再呼叫裡面的 PrintMessage()。
MessageControl msgctrl = new MessageControl();
msgctrl.PrintMessage();
但是,如果我要在各個地方呼叫 PrintMessage(),很多時候就必須再 new 一個實例出來,最終就會有一堆實例,並且他們做的事情可能都是一樣的。
要解決這個問題,就可以使用單例模式。讓實例只需在 class 內部建立一次,並開放一個全局接口取用,並且每次取用時先判斷實例是否已建立,已經建立的話就直接 return 建立好的那一個就可以了。
public class MessageControl
{
private static MessageControl instance; // 宣告實例
private MessageControl() { }
public static MessageControl GetInstance() // 全局接口
{
if (instance == null) { instance = new MessageControl(); } // 若未建立過,就建立實例
return instance;
}
// Method function
public void PrintMessage()
{
Console.WriteLine("Hello, I am Singleton");
}
}
如此一來,不管在任何地方,都只要呼叫此類別的 GetInstance(),他會幫我們視情況建立唯一的自己的實例。之後就可以安心地使用這個類別了。
MessageControl.GetInstance().PrintMessage();
「懶漢式」與「餓漢式」
上一段的例子屬於「懶漢式」(Lazy Initialization)。只有在初次使用的時候才會建立實例。
那我們可不可以在程式初始化的時候就將實例先建立出來? 當然可以,此情況也被稱為「餓漢式」(Eager Initialization)。懶漢比較懶,餓漢則比較積極的感覺。
public class MessageControl
{
private static MessageControl instance = new MessageControl();
private MessageControl() { }
public static MessageControl GetInstance()
{
return instance;
}
// Method function
// ......
}
就真的只是把 new MessageControl() 在宣告實例時一併建立而已。
雙重檢查鎖定(Double-Check Locking)
雖然說懶漢式可以在需要時才建立實例,但會有一個問題,那就是在多線程的情況下,若有同時來了兩個訪問,那就麻煩了,因此我們需要 「雙重檢查鎖定」(Double-Check Locking)。
public class MessageControl
{
private static MessageControl instance;
private static readonly object lockObject = new object();
private MessageControl() { }
public static MessageControl GetInstance()
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new MessageControl();
}
}
}
return instance;
}
// Method function
// ......
}
如此一來 lockObject 可以確保 lock 區域一次只會有一個線程進入。
靜態內部類
如果希望將內部靜態封裝(Static Inner Class),對外部進行隱藏的話,也可以。
public class MessageControl
{
// private 防止在外部實例化
private MessageControl() { }
private static class SingletonHolder{
internal static readonly MessageControl INSTANCE = new MessageControl();
}
public static MessageControl GetInstance()
{
return SingletonHolder.INSTANCE;
}
// Method function
// ......
}
以上。
评论区