[設計模式之禪讀書筆記]007_23種設計模式一:單例模式


   序言

   今天開始學習23種設計模式的第一個模式——單例模式,以前在網上也看過不少人寫的單例模式博文,也算已經吸收了不少。今天看《設計模式之禪》里對設計模式的講解,作者說的還挺細致的。唯一可惜的是作者用java講解單例模式,而我自己從去年工作后就用的是C++,本着學什么用什么的原則,我就用C++來記錄一下學習成果吧。希望這篇博客能把我學到的分享給跟我一樣在園子的讀者。

   正文

1. 單例模式的現實場景

   作者開篇用皇帝來類比單例,而我有個更好的例子,且聽我一一道來。太陽系中有一個恆星和九大行星,至少我上學的時候是九大行星,后來因為冥王星太小了,被除名了。可是我還是覺得九這個數字比較好,比如九大行星,九星連珠,說起來爽口。扯遠了,單例模式在這太陽系中相當於什么呢?太陽。是的,太陽。地球說,我要陽光;金星說,我要陽光。這些陽光都來自同一個太陽,也就是說關於太陽這個類的對象,整個太陽系只有一個。這就是單例模式,無論我在太陽系的哪個行星上,我得到的陽光都是來自同一個太陽的。

2. 單例模式實現(一)——非同步單例模式、懶漢式單例

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Singleton{
 5 public:
 6     static Singleton* getInstance(){
 7         if(instance == NULL){
 8             instance = new Singleton();
 9         }
10         return instance;
11     }
12 private:
13     static Singleton *instance;
14     Singleton(){}; // 使得該類不能在外部初始化
15 };
16 Singleton* Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義
17 
18 int main(){
19     Singleton *instance = Singleton::getInstance();
20     system("pause");
21     return 0;
22 }

源碼實現解讀

   上面的代碼是完整的控制台程序,VS2010 Pro下可以正確編譯運行。下面對這段程序的重點進行介紹一下:

      ■ 類Singleton擁有一個靜態成員指針變量,該指針為整個類共享

      ■ 類Singleton擁有一個私有的無參構造函數,這說明,在類外是無法通過構造方法來實例化對象的

      ■ 類Singleton擁有一個getInstance的方法,這個方法內部用於實例化instance成員指針變量

   上面的單例實現又被稱為懶漢式的單例模式,為什么呢?因為類Singleton的成員instance只有在被調用的時候才會被初始化,所以被稱為懶漢式單例實現。

問題缺點

   上面的實現有沒有問題呢?有。在多線程場景中,上面的代碼很容易產生多個實例,為什么呢?我畫了一張圖,如下:

    

   從上圖可以看出,在多線程環境下,如果還是按照上面的代碼實現的話,就會發生多次創建instance的情況,所以上面的代碼還是有問題的,不過僅僅是在多線程的環境下。

3. 單例模式實現(二)——同步單例模式、餓漢式單例

   針對2中出現的多線程問題,我們用下面的代碼就可以很輕易的結局。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Singleton{
 5 public:
 6     static Singleton getInstance(){
 7         return instance;
 8     }
 9 private:
10     static Singleton instance;
11     Singleton(){}; // 使得該類不能在外部初始化
12 };
13 Singleton Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義
14 
15 int main(){
16     Singleton instance = Singleton::getInstance();
17     system("pause");
18     return 0;
19 }

   上面的代碼在類創建的時候,實例instance就被創建了,這樣就不會發生多線程多次創建實例的情況了。程序解讀如下:

      ■ 類Singleton擁有一個靜態實例變量,而不是指針

      ■ 類Singleton擁有一個私有構造方法,這跟懶漢式是一樣的。

   上面的實現為什么叫惡漢式單例呢?因為上面的實現在調用Singleton的getInstance之前就應景實例化了一個靜態的屬於該類的靜態成員變量。

問題缺點

   上面的實現結局了多線程問題,那還有沒有問題呢?有。仔細觀察一下,發現有這樣一句話:Singleton instance = Singleton::getInstance(); 創建的對象通過Singleton的賦值運算符被賦值給了instance了,這就不好了,雖然對類Singleton中的instance來說,永遠只有一個。但是它被復制了,這樣就不是單例了,那怎么辦呢?辦法很簡單,就是把類Singleton的拷貝構造函數和賦值操作符變為private就可以了,修改后的代碼如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Singleton{
 5 public:
 6     static Singleton getInstance(){
 7         return instance;
 8     }
 9 private:
10     static Singleton instance;
11     Singleton(){}; // 使得該類不能在外部初始化
12     Singleton(const Singleton& singleton);
13     Singleton & operator = (const Singleton&);
14 };
15 Singleton Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義
16 
17 int main(){
18     //Singleton instance = Singleton::getInstance(); // 無法通過編譯
19     system("pause");
20     return 0;
21 }

4. 單例模式中對象的銷毀

   對於懶漢式的單例實現,我們在getInstance函數里看到了new字樣,那是肯定需要用delete銷毀的,否則會出現內存溢出的,那要怎樣用代碼來實現呢?追加一個析構函數就可以了,參考如下代碼:

 1 class Singleton{
 2 public:
 3     static Singleton* getInstance(){
 4         if(instance == NULL){
 5             instance = new Singleton();
 6         }
 7         return instance;
 8     }
 9     ~Singleton(){
10         if(instance != NULL){
11             delete instance;
12         }
13     }
14 private:
15     static Singleton* instance;
16     Singleton(){}; // 使得該類不能在外部初始化
17 };
18 Singleton* Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義

   其他

   在java中,解決多線程的問題可以有很多種,本文就不討論了,有興趣的可以參考:http://blog.csdn.net/it_man/article/details/5787567,這篇博客寫的代碼還是挺翔實的。

   總結

   在不考慮多線程的情況下,可以使用懶漢式單例實現方法。而在有多線程的環境下,最好用餓漢式單例實現方法。同時要注意的是,要把構造函數、默認構造函數、賦值運算符重載都改為private的。

PS:有錯誤的地方還請指出。謝謝。


免責聲明!

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



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