我們真的會使用單例模式嗎?


這篇博客的標題用了一個疑問句,源於我們公司的代碼評審,深刻的討論了單例模式的使用場景及其與靜態方法來說有何不同,這次討論確實讓我真正的理解了單例模式的使用,雖然說理解還一定全面,但必須作為一個認知的提升。告訴了我自己,對於編程,不懂的太多,原理性的東西還需要持續的學習。

進入正文,我們來討論一下,什么是單例模式,何時使用單例模式?

單例模式是經典設計模式的一種,熟悉設計模式或者說讀過設計模式相關書籍的同事都知道,這應該算是設計模式中最簡單、最容易理解、使用最廣泛的一種。單例模式主要是用來實現一個類的實例全局唯一,使用double check的形式來定義。

 
         
         
        
 1 public class SingleInstance
 2     {
 3         private static readonly object _lock = new object();
 4         private static SingleInstance _instance = null;
 5 
 6         /// <summary>
 7         /// 私有構造函數
 8         /// </summary>
 9         private SingleInstance() { }
10 
11         /// <summary>
12         /// 單一實例
13         /// </summary>
14         /// <returns></returns>
15         public static SingleInstance GetInstance()
16         {
17             if (_instance == null)
18             {
19                 lock (_lock)
20                 {
21                     if (_instance == null)
22                     {
23                         _instance = new SingleInstance();
24                     }
25                 }
26             }
27             return _instance;
28         }
29 
30 
31         public  void Show()
32         {
33             Console.WriteLine("輸出。。。郭志奇");
34         }
35 
36         public  void Speak()
37         {
38             Console.WriteLine("說話。。。郭志奇");
39         }
40     }
 
        
單例模式使用了私有構造函數來保證外部無法實例化、使用double check來保證實例被唯一創建。這是一個基本的單例模式寫法,我一般會在其中寫一些方法來進行調用,主要是為了避免每次調用都需要new的麻煩。但其中存在一些問題,如果采用靜態方法來寫:
1        public static void Show()
2         {
3             Console.WriteLine("輸出。。。郭志奇");
4         }
5 
6         public static void Speak()
7         {
8             Console.WriteLine("說話。。。郭志奇");
9         }

 

比較這兩種調用,其實使用方式是一致的,但單例模式會在程序運行中一直存在,不會被銷毀,因為單例模式中使用到了靜態變量,靜態變量的使用會導致實例不會被銷毀。但這也不應該是單例模式的缺點。

但我為什么會說我們真的懂單例模式?

回到開頭,我們說單例模式,為什么我們需要單例模式,絕對不是因為方便調用,因為靜態方法更方便。那到底為什么使用單例模式呢?其實經過我們的討論,單例模式的使用場景是一些全局不可變參數,可以放到單例中,比如從配置獲取值,然后緩存到單例中,這才是我們應當使用單例的場景,千萬別像我,為了使用方便而無節制的使用單例。

使用單例,方便調用,但會造成什么問題呢?

要回答這個問題,我們首先回憶一下GC的垃圾回收機制,垃圾回收分為三代,如果類中包含靜態成員,垃圾回收機制是不會回收的,也就意味着如果我們無節制的使用單例,會造成程序運行過程中出現大量的實例不會被銷毀,會無意識的造成內存使用增高。    如果采用懶加載的方式,在單例未被調用的時候,不會實例化,如果采用餓漢加載的話,那么在程序初始化的時候,就會被初始化,無疑會加重程序的初始化成本,增加啟動時間。

如果我們僅僅是為了方便調用,可以使用靜態方法。

上面我們說了懶加載方式,我們來代碼說明一下餓漢模式的加載方式:

 1   public class SingleInstance
 2     {
 3         private static readonly object _lock = new object();
 4         private static SingleInstance _instance = new SingleInstance();
 5 
 6         /// <summary>
 7         /// 私有構造函數
 8         /// </summary>
 9         private SingleInstance() { }
10 
11         /// <summary>
12         /// 單一實例
13         /// </summary>
14         /// <returns></returns>
15         public static SingleInstance GetInstance()
16         {            
17             return _instance;
18         }
19 
20 
21         public void Show()
22         {
23             Console.WriteLine("輸出。。。郭志奇");
24         }
25 
26         public void Speak()
27         {
28             Console.WriteLine("說話。。。郭志奇");
29         }
30     }

餓漢模式的加載就是靜態成員在定義的時候即初始化。

總結:

1、我們應該選擇合適的時機使用單例模式,不要無節制的使用,應該明白何時才應該使用單例模式。

2、盡量避免靜態成員的使用,因為靜態成員所在的實例,不會被GC回收。

3、優先選擇靜態方法調用而不是單例模式調用。

4、如果必須使用單例模式,盡量采用懶加載,而不是餓漢加載的方式,減少程序啟動成本。

引申:

1、我們使用了lock(object)來鎖定一個變量,達到加鎖的目的,避免多個線程同時對實例執行初始化。那么如果我們lock(string 字符串類型)是否可以呢?答案是否定。

2、System.String和string有什么不同呢?

歡迎有不同見解的同事可以回復討論,知識總是在討論中得到升華。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM