1. 程式人生 > >入門小怪——單例模式

入門小怪——單例模式

一、前言

  在上一節中我們對設計模式進行了一定的介紹及分類。設計模式分為建立型、結構型、行為型。建立型模式——主要負責物件的建立。結構型職責——主要負責處理類與物件的組合。行為型模式——主要負責類與物件互動中的職責的分配問題。今天我們也是講述介紹建立型模式中的第一個模式——單例模式。

二、單例模式介紹

  (一)來由

    單例模式(Singleton Pattern)是最簡單的一個設計模式 ,這種設計模式屬於建立型模式。在程式中總會有一些特殊的類。它們必須保證在系統中只存在一個例項,這個單一的類自己建立自己的物件,同時確保只有單個物件被建立,並且提供唯一的訪問形式。可以直接進行訪問,不用再新建例項。

    那麼如何避開常規的設計,來實現一個類一個例項、並且保證唯一呼叫呢?這時候就是單例模式施展身手的時候了。

  (二)意圖

    保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

  (三)單例模式實現方法

    單例模式到底又是如何實現的呢?既然是單一例項,那麼隊友多執行緒又該如何處理呢?下面我們一一來看看單例模式的實現。單例模式我們又涉及到其實現的多種形式——非執行緒安全、執行緒安全、雙重驗證執行緒安全、不用鎖執行緒安全、完全延遲載入、使用.NET4的Lazy<T>型別。

      1. 非執行緒安全

   

    /// <summary>
    /// 非執行緒安全
    /// </summary>
    public sealed class Singleton1
    {
        /// <summary>
        /// 定義靜態變數儲存例項
        /// </summary>
        public static Singleton1 Instance = null;

        /// <summary>
        /// 定義私有建構函式保護,使其他地方不得例項
        /// </summary>
        private Singleton1()
        { 
        }
        public string GetString()
        {
          return  "非執行緒安全的單例模式";
        } 

        /// <summary>
        /// 定義公共方法,實現全域性訪問
        /// </summary>
        /// <returns></returns>
        public static Singleton1 GetInstance()
        {
            //判斷例項狀態
            if (Instance==null)
            {
                Instance = new Singleton1();
            }
            return Instance;
        }
    }
View Code

 

    在上述事例中完美的實現了單執行緒的單例模式的情況。這裡我們也需要注意一些的情況:

① 單例類包含一個private的私有建構函式

② 類申明sealed 密封不可繼承(不強制)

③ 類中有一個靜態變數儲存例項

④ 類中提供有一個靜態方法或者屬性實現例項的建立引用全域性呼叫訪問

⑤ 在多執行緒中單例模式需要另行處理,不然有可能得到類的多個例項

 

2. 執行緒安全

    /// <summary>
    /// 執行緒安全單例模式
    /// </summary>
    public sealed class Singleton2
    {
        /// <summary>
        /// 定義靜態變數儲存例項
        /// </summary>
        public static Singleton2 Instance = null;

        private static readonly object locks=new object();

        /// <summary>
        /// 定義私有建構函式保護,使其他地方不得例項
        /// </summary>
        private Singleton2()
        {
        }
        public string GetString()
        {
            return "執行緒安全的單例模式";
        }
        /// <summary>
        /// 定義公共方法,實現全域性訪問
        /// </summary>
        /// <returns></returns>
        public static Singleton2 GetInstance()
        {
            //對執行緒進行加鎖限制,掛起後來的執行緒。保證例項安全
            lock (locks)
            {
                if (Instance == null)
                {
                    Instance = new Singleton2();
                }
            } 
            return Instance;
        }
    }
View Code

 

      3. 不用鎖執行緒安全

 /// <summary>
    /// 不用鎖執行緒安全單例模式
    /// </summary>
    public sealed class Singleton3
    {
        /// <summary>
        /// 定義靜態變數儲存例項
        /// </summary>
        private static readonly Singleton3 Instance = new Singleton3 ();

        static Singleton3()
        { }
        /// <summary>
        /// 定義私有建構函式保護,使其他地方不得例項
        /// </summary>
        private Singleton3()
        {
        }
        public string GetString()
        {
            return "不用鎖執行緒安全單例模式";
        }
        /// <summary>
        /// 定義公共方法,實現全域性訪問
        /// </summary>
        /// <returns></returns>
        public static Singleton3 GetInstance()
        {   
            return Instance;
        }
    }
View Code

 

      這個實現方法沒有使用到鎖,但是也實現了執行緒安全。在第一次呼叫的時候會建立一個instance。這個實現也有一定的安全隱患。

      1. instance被建立的時機不明,任何對Singleton的呼叫都會提前建立instance
      2. static建構函式的迴圈呼叫。如有A,B兩個類,A的靜態建構函式中呼叫了B,而B的靜態建構函式中又呼叫了A,這兩個就會形成一個迴圈呼叫,嚴重的會導致程式崩潰。
      3. 我們需要手動新增Singleton的靜態建構函式來確保Singleton型別不會被自動加上beforefieldinit這個Attribute,以此來確保instance會在第一次呼叫Singleton時才被建立。
      4. readonly的屬性無法在執行時改變,如果我們需要在程式執行時dispose這個instance再重新建立一個新的instance,這種實現方法就無法滿足。

 

      4. 完全延遲載入

    /// <summary>
    /// 實現完全延遲載入單例模式
    /// </summary>
    public sealed class Singleton4
    {
        /// <summary>
        /// 定義私有建構函式保護,使其他地方不得例項
        /// </summary>
        private Singleton4()
        {
        }
        /// <summary>
        ///  提供訪問位置
        /// </summary>
        public static Singleton4 Instance {
            get
            {
                return GetInstance.instance;
            }
        }
        /// <summary>
        /// 定義私有類確保第一次載入是初始化及呼叫
        /// </summary>
        private class GetInstance
        {
            static GetInstance(){}
            internal static readonly Singleton4 instance = new Singleton4();
        }
         
        public string GetString()
        {
            return "實現完全延遲載入單例模式";
        }
        
    }
View Code

 

      它確保了instance只會在Instance的get方法裡面呼叫,且只會在第一次呼叫前初始化。是上一個版本的延遲載入的版本

      5. 使用.NET4的Lazy<T>型別

 

    /// <summary>
    /// 使用Lazy<T>實現完全延遲載入單例模式
    /// </summary>
    public sealed class Singleton5
    {
        /// <summary>
        /// 延遲載入初始化
        /// </summary>
        private static readonly Lazy<Singleton5> lazy=new Lazy<Singleton5>(()=>new Singleton5());

        /// <summary>
        /// 定義私有建構函式保護,使其他地方不得例項
        /// </summary>
        private Singleton5()
        {
        }

        /// <summary>
        /// 提供全域性訪問點
        /// </summary>
        /// <returns></returns>
        public static Singleton5 Instance()
        {
            return lazy.Value;
        }
        public string GetString()
        {
            return "實現完全延遲載入單例模式";
        }

    }
View Code

 

 

 

      在.NET4.0中,可以使用Lazy<T> 來實現物件的延遲初始化,從而優化系統的效能。延遲初始化就是將物件的初始化延遲到第一次使用該物件時。延遲初始化是我們在寫程式時經常會遇到的情形,例如建立某一物件時需要花費很大的開銷,而這一物件在系統的執行過程中不一定會用到,這時就可以使用延遲初始化,在第一次使用該物件時再對其進行初始化,如果沒有用到則不需要進行初始化,這樣的話,使用延遲初始化就提高程式的效率,從而使程式佔用更少的記憶體。

三、使用場合及優缺點

  一、使用場合

1、當類只需要且只能有一個例項並且需要全域性訪問的時候。

2、當類是使用子類化擴充套件,並且無需更改程式碼就可以使用擴充套件例項的情況下。

二、優點

1、控制例項數量:保證例項數量的唯一且是全域性訪問。

2、靈活性:類控制了例項化的全過程,這樣可以更加靈活的修改例項化過程

3、資源節省:避免對資源的多重佔用

三、缺點

1、沒有介面、也不能繼承。這個與單一責任原則相沖突,一個類只應該負責其邏輯,而不應該去負責如何例項。

四、總結

  在設計模式的學習過程中,單例模式較為簡單,實現操作並不是特別難,但是在我們例項運用中也當注意下,比較如果使用出現問題。找到問題還是稍微困難的。這篇文章也介紹了幾種單例模式的使用方法,在我們使用時擇優選取最佳方案。下一節我們將為全面講述二級野怪、並學習攻克它。

  生命不息、戰鬥不止!

  C#設計模式系列目錄

歡迎大家掃描下方二維碼,和我一起踏上設計模式的闖關之路吧!

  

&n