設計模式之單例

—阿輝發表於2021-08-15

單例模式介紹

單例模式主要解決的是,一個全域性使用的類頻繁的建立和消費,從而提升整體程式碼的效能。

在我們平時使用中,要確保一個類只能有一個例項物件,即使多執行緒同時訪問,也只能建立一個例項物件,並需要提供一個全域性訪問此例項的點。

用來建立獨一無二的,只能有一個例項物件的入場卷。

單例模式允許在程式的任何地方訪問特定物件,但是它可以保護該例項不被其他程式碼覆蓋。

使用場景:

  • 控制某些共享資源的訪問許可權(連線資料庫、訪問特殊檔案)
  • 某些全域性的屬性或變數想保持其唯一性,可使用
  • 程式中的某個類對於所有客戶端只有一個可用的例項,可以使用單例模式

程式碼結構:

  • 將預設的建構函式設為私有,防止其他物件使用單例類的new運算子。
  • 會有一個靜態構造方法作為建構函式。該函式會偷偷呼叫私有建構函式來建立物件,並將其儲存在一個靜態成員變數中。後面所有對該函式的呼叫都將返回這一快取物件。

實現方式:

  • 在類中宣告一個私有靜態成員變數用於儲存單例模式
  • 宣告一個公有靜態構建方法用於獲取單例
  • 在靜態方法中實現“延遲初始化”,該方法會在首次被呼叫時建立一個新物件,並將其儲存在靜態成員變數中,此後該方法每次被呼叫時都返回該例項
  • 將類的建構函式私有私有,防止其外部物件宣告呼叫

單例模式優缺點

優點:

  • 可以保證一個類只有一個例項
  • 獲得了一個指向該例項的全域性訪問節點
  • 僅在首次請求單例物件時對其進行初始化

缺點:

  • 違反了“單一職責原則”(該模式同時解決了兩個問題)
  • 該模式在多執行緒中需特殊處理,避免多個執行緒多次建立單例物件
  • 單元測試比較困難,無法新建宣告新的測試物件。

Demo

單例可以分很多實現方式,但是從大類上來劃分,主要為懶漢模式和餓漢模式

懶漢模式
  • 懶漢模式(執行緒不安全)
    /// <summary>
    /// 單例模式 (常規用法,執行緒不安全。)
    /// </summary>
    public class Singleton
    {       
        /// <summary>
        /// 私有建構函式
        /// </summary>
        private Singleton()
        {

        }
        /// <summary>
        /// 靜態區域性變數
        /// </summary>
        private static Singleton _instance=null;
        /// <summary>
        /// 靜態的全域性唯一訪問口
        /// 只能得到快取的靜態區域性變數例項,無法重新新建。
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance() 
        {
            if (_instance==null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
    

此方法滿足了懶載入,但是如果多個訪問者同時進行訪問獲取物件,就會出現多個例項物件,就不是單例模式了,所以此方法在多執行緒場景是不實用的。

  • 懶漢模式(執行緒安全)
    /// <summary>
    /// 單例模式 (常規用法,執行緒安全。)
    /// </summary>
    public class Singleton
    {       
        /// <summary>
        /// 私有建構函式
        /// </summary>
        private Singleton()
        {
        }
        /// <summary>
        /// 靜態區域性變數
        /// </summary>
        private static Singleton _instance=null;
        /// <summary>
        /// 宣告鎖 鎖同步
        /// </summary>
        private static readonly object _lock = new object();
        /// <summary>
        /// 靜態的全域性唯一訪問口
        /// 只能得到快取的靜態區域性變數例項,無法重新新建。
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance(string value) 
        {  
                if (_instance == null)                //雙重判空,確保在多執行緒狀態下只建立一個例項物件
                {
                    lock (_lock)                                 //加鎖,確保其每次只能由一個物件進行訪問
                    {
                        if (_instance==null)
                        {
                            _instance = new Singleton();
                            _instance.Value = value;
                        }
                    }                    
                }            
            return _instance;
        }

        public string Value { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("多執行緒進行訪問");
            Thread processOne = new Thread(() => {
                TestSingleton("阿輝");
            });

            Thread processTwo = new Thread(() => {
                TestSingleton("阿七");
            });

            processOne.Start();
            processTwo.Start();

            processOne.Join();
            processTwo.Join();

            Console.ReadKey();
        }

        static void TestSingleton(string value) 
        {
            Singleton s = Singleton.GetInstance(value);
            Console.WriteLine(s.Value);
        }
    }

可以看到在我們模擬的多執行緒訪問過程中,即使設定的阿輝和阿七,最後輸出的也只有阿輝,也就是說只能建立一個例項物件。

可以看到懶載入就是程式剛開始不例項化,只有在被呼叫或者需要使用它的時候才進行例項化操作,這就是懶載入。

餓漢模式
  • 使用靜態變數實現單例 ------餓漢模式(執行緒安全)
    public class Singleton1
    {
        /// <summary>
        /// 餓漢式,也就是在程式執行時都已經進行了初始化操作,後續只是呼叫而已。
        /// </summary>
        private static Singleton1 instance = new Singleton1();
        
        private Singleton1()
        {
                
        }

        public static Singleton1 GetInstance() 
        {
            return instance;
        }                   
    }

餓漢式顧名思義就是提前都惡的不行了,在程式剛開始啟動的時候就已經將其進行了例項化,後續只管呼叫。

這種不是懶載入,無論程式是否會用到這個類,它都會提早的進行例項化。

  • 利用靜態建構函式實現單例模式(執行緒安全)
    public class Singleton2 
    {
        private static Singleton2 _Singleton2 = null;

        static Singleton2() {
            _Singleton2 = new Singleton2();
        }

        public static Singleton2 CreateInstance() 
        {
            return _Singleton2;
        }
    }

單例模式常用的就大體介紹完了,我們在平時的使用過程中需要根據不同的業務邏輯來選擇不同的實現方式,不能說哪一種最好,也不能說哪一種不好,只是它們使用的場景不同而已。

小寄語

人生短暫,我不想去追求自己看不見的,我只想抓住我能看的見的。

我是阿輝,感謝您的閱讀,如果對你有幫助,麻煩點贊、轉發 謝謝。

相關文章