確保一個類別只有一個實體存在,並且給予一全域的存取點。
官方說明
確保一個類別只有一個實體存在,並且給予一全域的存取點。
主要療效
當該類別只允許一個實體物件存在時,就可套用此模式來達到單一類別實體的特性。亦或許可用單一全域變數來實現此需求,但此作法的規範其實是鬆散的,且需要透過開發人員的默契來配合;另外,程式需在一開始就要建立好物件(無論是否有被使用到),進而造成不必要的資源浪費。
關鍵心法
- 私有的建構子
- 私有靜態(Static)欄位 - 紀錄自身類別實體
- 公開靜態(Static)方法 - 實體化物件 + 傳回實體
應用聯想
相信大家在開發系統時或多或少都有設定檔(Config)的需求,而設定檔通常都是共用的,並且只會有一份存在(如果多份系統將會無所適從+錯亂)。各位有沒有注意到此時關鍵字"一份"出現了,所以筆者將以設定檔類別套用Singleton Pattern來約束單一實體的特性。
首先試著搭配關鍵心法來實現設定檔的獨體:
- 私有的建構子 [ private ConfigSingleton() ]
- 私有靜態(Static)欄位 - 紀錄自身類別實體 [ private static ConfigSingleton _uniqueConfigSingleton ]
- 公開靜態(Static)方法 - 實體化物件 + 傳回實體 [ public static ConfigSingleton GetInstance() ]
PS. Singleton Pattern只是著重在取得(建立)該類別實體的方法,至於類別所需要的方法及屬性當然就依照類別特性來設計,就如同本範例中的 SaveConfig()及LoadConfig()方法。
public class ConfigSingleton
{
// Fields
private static ConfigSingleton _uniqueConfigSingleton;
// Constructors
private ConfigSingleton()
{
LoadConfig();
}
// Properties
public string ConnectionStr { get; set; }
// Methods
public static ConfigSingleton GetInstance()
{
if (_uniqueConfigSigleton == null)
_uniqueConfigSingleton = new ConfigSingleton();
return _uniqueConfingSigleton;
}
public bool SaveConfig()
{
// save config data
return true;
}
public bool LoadConfig()
{
// load config data
return true;
}
private string GetConnStr()
{
return ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
}
}
此時我們就已完成一個套用Singleton Pattern的設定檔類別
當我們需要操作此物件時,只需要呼叫 ConfigSingleton.GetInstance()即可取得實體。仔細看看GetInstance()方法,當第一次呼叫GetInstance()時才會建立出實體[ new ConfigSingleton() ],表示只有在需要時才會產生,我們稱之拖延實體化(Lazy Instantiaze)。
考量多執行緒的狀況
就當一切看似很美好的狀況下,咱們來考量多執行緒的情況。如下示意圖所示,當ThreadA與ThreadB同時透過GetInstance()方法要取得實體,剛好ConfigSingleton都尚未被取用過(_uniqueConfigSingleton==null),又恰巧同時符合條件並進入判斷式內,就會造成ThreadA與ThreadB取到不同的實體,錯誤就可能從此蔓延開來了。所以我們應該要避免以下的狀況(Non-thread-safe)產生。
有三種方式可以來避免這情況的產生:
1. 使用Lock方式來避免多個執行緒同時進入該程式片段。但由於每次取得實體時,都需要經過lock的檢驗,所以多少都會對於效能上有所衝擊,尤其是很頻繁的使用此物件實體的情況下;所以我們在進入lock前再透過多一層的檢驗[ if (_uniqueConfigSigleton == null) ],使得在實體已被建立後(非初次使用)的情況下不再受到lock的檢驗,巧妙地避免掉對於效能上的衝擊。
public class ConfigSingleton
{
// Fields
private static ConfigSingleton _uniqueConfigSingleton;
private static readonly object padlock = new object();
// Constructors
private ConfigSingleton()
{
LoadConfig();
}
// Properties
public string ConnectionStr { get; set; }
// Methods
public static ConfigSingleton GetInstance()
{
if (_uniqueConfigSingleton == null)
{
lock (padlock)
{
if (_uniqueConfigSingleton == null)
_uniqueConfigSigleton = new ConfigSingleton();
}
}
return _uniqueConfigSingleton;
}
public bool SaveConfig()
{
// save config data
return true;
}
public bool LoadConfig()
{
// load config data
return true;
}
private string GetConnStr()
{
return ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
}
}
2. 放棄使用拖延實體化(Lazy Instantiaze)的方式建構實體,直接建立實體以避免多執行緒的問題。唯一的缺點就是不論是否有被使用到,都會把實體建立出來,造成不必要的資源損失。
public class ConfigSingleton
{
// Fields
private static readonly ConfigSingleton _uniqueConfigSingleton =
new ConfigSingleton();
// Constructors
private ConfigSingleton()
{
LoadConfig();
}
// Properties
public string ConnectionStr { get; set; }
// Methods
public static ConfigSingleton GetInstance()
{
return _uniqueConfigSingleton;
}
public bool SaveConfig()
{
// save config data
return true;
}
public bool LoadConfig()
{
// load config data
return true;
}
private string GetConnStr()
{
return ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
}
}
3. 在.Net Framework 4 開始提供了拖延實體化(Lazy Instantiaze)的支援,透過System.Lazy<T>可以簡化許多的工作,只要傳入建構子的委派即可達到我們的目的。改良了原本為了避免多執行緒的問題,直接建立實體而造成不必要的資源損失。
public class ConfigSingleton
{
// Fields
private static readonly Lazy<ConfigSingleton> _lazyUniqueConfigSingleton =
new Lazy<ConfigSingleton>(() => new ConfigSingleton());
// Constructors
private ConfigSingleton()
{
LoadConfig();
}
// Properties
public string ConnectionStr { get; set; }
// Methods
public static ConfigSingleton GetInstance()
{
return _lazyUniqueConfigSingleton.Value;
}
public bool SaveConfig()
{
// save config data
return true;
}
public bool LoadConfig()
{
// load config data
return true;
}
private string GetConnStr()
{
return ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
}
}
參考資訊
http://csharpindepth.com/articles/general/singleton.aspx
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !