[Design Pattern] Singleton Pattern

確保一個類別只有一個實體存在,並且給予一全域的存取點。

官方說明

確保一個類別只有一個實體存在,並且給予一全域的存取點。

 

主要療效

建立獨一無二的類別實體!!

當該類別只允許一個實體物件存在時,就可套用此模式來達到單一類別實體的特性。亦或許可用單一全域變數來實現此需求,但此作法的規範其實是鬆散的,且需要透過開發人員的默契來配合;另外,程式需在一開始就要建立好物件(無論是否有被使用到),進而造成不必要的資源浪費。

 

關鍵心法

  1. 私有的建構子
  2. 私有靜態(Static)欄位 - 紀錄自身類別實體
  3. 公開靜態(Static)方法 - 實體化物件 + 傳回實體

 

應用聯想

相信大家在開發系統時或多或少都有設定檔(Config)的需求,而設定檔通常都是共用的,並且只會有一份存在(如果多份系統將會無所適從+錯亂)。各位有沒有注意到此時關鍵字"一份"出現了,所以筆者將以設定檔類別套用Singleton Pattern來約束單一實體的特性。

首先試著搭配關鍵心法來實現設定檔的獨體:

  1. 私有的建構子 [ private ConfigSingleton() ]
  2. 私有靜態(Static)欄位 - 紀錄自身類別實體 [ private static ConfigSingleton _uniqueConfigSingleton ]
  3. 公開靜態(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)產生。

not 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


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !