設計模式(二)單例模式(轉)

原文地址:http://www.jellythink.com/archives/82ios

問題描述

如今,無論開發一個多大的系統(至少我如今的部門是這樣的),都會帶一個日誌功能;在實際開發過程當中,會專門有一個日誌模塊,負責寫日誌,因爲在系統的任何地方,咱們都有可能要調用日誌模塊中的函數,進行寫日誌。那麼,如何構造一個日誌模塊的實例呢?難道,每次new一個日誌模塊實例,寫完日誌,再delete,不要告訴我你是這麼幹的。在C++中,能夠構造一個日誌模塊的全局變量,那麼在任何地方就均可以用了,是的,不錯。可是,我所在的開發部門的C++編碼規範是參照Google的編碼規範的。程序員

全局變量在項目中是能不用就不用的,它是一個定時炸彈,是一個不安全隱患,特別是在多線程程序中,會有不少的不可預測性;同時,使用全局變量,也不符合面向對象的封裝原則,因此,在純面向對象的語言Java和C#中,就沒有純粹的全局變量。那麼,如何完美的解決這個日誌問題,就須要引入設計模式中的單例模式。數據庫

單例模式

何爲單例模式,在GOF的《設計模式:可複用面向對象軟件的基礎》中是這樣說的:保證一個類只有一個實例,並提供一個訪問它的全局訪問點。首先,須要保證一個類只有一個實例;在類中,要構造一個實例,就必須調用類的構造函數,如此,爲了防止在外部調用類的構造函數而構造實例,須要將構造函數的訪問權限標記爲protected或private;最後,須要提供要給全局訪問點,就須要在類中定義一個static函數,返回在類內部惟一構造的實例。意思很明白,使用UML類圖表示以下。編程

UML類圖

Singleton Pattern

代碼實現

單例模式,單從UML類圖上來講,就一個類,沒有錯綜複雜的關係。可是,在實際項目中,使用代碼實現時,仍是須要考慮不少方面的。設計模式

實現一:安全

 1 /*
 2 ** FileName     : SingletonPatternDemo1
 3 ** Author       : Jelly Young
 4 ** Date         : 2013/11/20
 5 ** Description  : More information, please go to http://www.jellythink.com
 6 */
 7 
 8 #include <iostream>
 9 using namespace std;
10 
11 class Singleton
12 {
13 public:
14     static Singleton *GetInstance()
15     {
16         if (m_Instance == NULL )
17         {
18             m_Instance = new Singleton ();
19         }
20         return m_Instance;
21     }
22 
23     static void DestoryInstance()
24     {
25         if (m_Instance != NULL )
26         {
27             delete m_Instance;
28             m_Instance = NULL ;
29         }
30     }
31 
32     // This is just a operation example
33     int GetTest()
34     {
35         return m_Test;
36     }
37 
38 private:
39     Singleton(){ m_Test = 10; }
40     static Singleton *m_Instance;
41     int m_Test;
42 };
43 
44 Singleton *Singleton ::m_Instance = NULL;
45 
46 int main(int argc , char *argv [])
47 {
48     Singleton *singletonObj = Singleton ::GetInstance();
49     cout<<singletonObj->GetTest()<<endl;
50 
51     Singleton ::DestoryInstance();
52     return 0;
53 }

 

這是最簡單,也是最廣泛的實現方式,也是如今網上各個博客中記述的實現方式,可是,這種實現方式,有不少問題,好比:沒有考慮到多線程的問題,在多線程的狀況下,就可能建立多個Singleton實例,如下版本是改善的版本。多線程

實現二:ide

 1 /*
 2 ** FileName     : SingletonPatternDemo2
 3 ** Author       : Jelly Young
 4 ** Date         : 2013/11/20
 5 ** Description  : More information, please go to http://www.jellythink.com
 6 */
 7 
 8 #include <iostream>
 9 using namespace std;
10 
11 class Singleton
12 {
13 public:
14     static Singleton *GetInstance()
15     {
16         if (m_Instance == NULL )
17         {
18             Lock(); // C++沒有直接的Lock操做,請使用其它庫的Lock,好比Boost,此處僅爲了說明
19             if (m_Instance == NULL )
20             {
21                 m_Instance = new Singleton ();
22             }
23             UnLock(); // C++沒有直接的Lock操做,請使用其它庫的Lock,好比Boost,此處僅爲了說明
24         }
25         return m_Instance;
26     }
27 
28     static void DestoryInstance()
29     {
30         if (m_Instance != NULL )
31         {
32             delete m_Instance;
33             m_Instance = NULL ;
34         }
35     }
36 
37     int GetTest()
38     {
39         return m_Test;
40     }
41 
42 private:
43     Singleton(){ m_Test = 0; }
44     static Singleton *m_Instance;
45     int m_Test;
46 };
47 
48 Singleton *Singleton ::m_Instance = NULL;
49 
50 int main(int argc , char *argv [])
51 {
52     Singleton *singletonObj = Singleton ::GetInstance();
53     cout<<singletonObj->GetTest()<<endl;
54     Singleton ::DestoryInstance();
55 
56     return 0;
57 }

 

此處進行了兩次m_Instance == NULL的判斷,是借鑑了Java的單例模式實現時,使用的所謂的「雙檢鎖」機制。由於進行一次加鎖和解鎖是須要付出對應的代價的,而進行兩次判斷,就能夠避免屢次加鎖與解鎖操做,同時也保證了線程安全。可是,這種實現方法在平時的項目開發中用的很好,也沒有什麼問題?可是,若是進行大數據的操做,加鎖操做將成爲一個性能的瓶頸;爲此,一種新的單例模式的實現也就出現了。svn

實現三:函數

 1 /*
 2 ** FileName     : SingletonPatternDemo3
 3 ** Author       : Jelly Young
 4 ** Date         : 2013/11/20
 5 ** Description  : More information, please go to http://www.jellythink.com
 6 */
 7 
 8 #include <iostream>
 9 using namespace std;
10 
11 class Singleton
12 {
13 public:
14     static Singleton *GetInstance()
15     {
16         return const_cast <Singleton *>(m_Instance);
17     }
18 
19     static void DestoryInstance()
20     {
21         if (m_Instance != NULL )
22         {
23             delete m_Instance;
24             m_Instance = NULL ;
25         }
26     }
27 
28     int GetTest()
29     {
30         return m_Test;
31     }
32 
33 private:
34     Singleton(){ m_Test = 10; }
35     static const Singleton *m_Instance;
36     int m_Test;
37 };
38 
39 const Singleton *Singleton ::m_Instance = new Singleton();
40 
41 int main(int argc , char *argv [])
42 {
43     Singleton *singletonObj = Singleton ::GetInstance();
44     cout<<singletonObj->GetTest()<<endl;
45     Singleton ::DestoryInstance();
46 }

 

由於靜態初始化在程序開始時,也就是進入主函數以前,由主線程以單線程方式完成了初始化,因此靜態初始化實例保證了線程安全性。在性能要求比較高時,就可使用這種方式,從而避免頻繁的加鎖和解鎖形成的資源浪費。因爲上述三種實現,都要考慮到實例的銷燬,關於實例的銷燬,待會在分析。由此,就出現了第四種實現方式:

實現四:

 1 /*
 2 ** FileName     : SingletonPatternDemo4
 3 ** Author       : Jelly Young
 4 ** Date         : 2013/11/20
 5 ** Description  : More information, please go to http://www.jellythink.com
 6 */
 7 
 8 #include <iostream>
 9 using namespace std;
10 
11 class Singleton
12 {
13 public:
14     static Singleton *GetInstance()
15     {
16         static Singleton m_Instance;
17         return &m_Instance;
18     }
19 
20     int GetTest()
21     {
22         return m_Test++;
23     }
24 
25 private:
26     Singleton(){ m_Test = 10; };
27     int m_Test;
28 };
29 
30 int main(int argc , char *argv [])
31 {
32     Singleton *singletonObj = Singleton ::GetInstance();
33     cout<<singletonObj->GetTest()<<endl;
34 
35     singletonObj = Singleton ::GetInstance();
36     cout<<singletonObj->GetTest()<<endl;
37 }

 

以上就是四種主流的單例模式的實現方式,若是你們還有什麼好的實現方式,但願你們能推薦給我。謝謝了。

實例銷燬

在上述的四種方法中,除了第四種沒有使用new操做符實例化對象之外,其他三種都使用了;咱們通常的編程觀念是,new操做是須要和delete操做進行匹配的;是的,這種觀念是正確的。在上述的實現中,是添加了一個DestoryInstance的static函數,這也是最簡單,最普通的處理方法了;可是,不少時候,咱們是很容易忘記調用DestoryInstance函數,就像你忘記了調用delete操做同樣。因爲怕忘記delete操做,因此就有了智能指針;那麼,在單例模型中,沒有「智能單例」,該怎麼辦?怎麼辦?

那我先從實際的項目中提及吧,在實際項目中,特別是客戶端開發,實際上是不在意這個實例的銷燬的。由於,全局就這麼一個變量,全局都要用,它的生命週期伴隨着軟件的生命週期,軟件結束了,它也就天然而然的結束了,由於一個程序關閉以後,它會釋放它佔用的內存資源的,因此,也就沒有所謂的內存泄漏了。可是,有如下狀況,是必須須要進行實例銷燬的:

  1. 在類中,有一些文件鎖了,文件句柄,數據庫鏈接等等,這些隨着程序的關閉而不會當即關閉的資源,必需要在程序關閉前,進行手動釋放;
  2. 具備強迫症的程序員。

以上,就是我總結的兩點。

雖然,在代碼實現部分的第四種方法能知足第二個條件,可是沒法知足第一個條件。好了,接下來,就介紹一種方法,這種方法也是我從網上學習而來的,代碼實現以下:

 1 /*
 2 ** FileName     : SingletonPatternDemo5
 3 ** Author       : Jelly Young
 4 ** Date         : 2013/11/20
 5 ** Description  : More information, please go to http://www.jellythink.com
 6 */
 7 
 8 #include <iostream>
 9 using namespace std;
10 
11 class Singleton
12 {
13 public:
14     static Singleton *GetInstance()
15     {
16         return m_Instance;
17     }
18 
19     int GetTest()
20     {
21         return m_Test;
22     }
23 
24 private:
25     Singleton(){ m_Test = 10; }
26     static Singleton *m_Instance;
27     int m_Test;
28 
29     // This is important
30     class GC
31     {
32     public :
33         ~GC()
34         {
35             // We can destory all the resouce here, eg:db connector, file handle and so on
36             if (m_Instance != NULL )
37             {
38                 cout<< "Here is the test" <<endl;
39                 delete m_Instance;
40                 m_Instance = NULL ;
41             }
42         }
43     };
44     static GC gc;
45 };
46 
47 Singleton *Singleton ::m_Instance = new Singleton();
48 Singleton ::GC Singleton ::gc;
49 
50 int main(int argc , char *argv [])
51 {
52     Singleton *singletonObj = Singleton ::GetInstance();
53     cout<<singletonObj->GetTest()<<endl;
54 
55     return 0;
56 }

 

在程序運行結束時,系統會調用Singleton的靜態成員GC的析構函數,該析構函數會進行資源的釋放,而這種資源的釋放方式是在程序員「不知道」的狀況下進行的,而程序員不用特別的去關心,使用單例模式的代碼時,沒必要關心資源的釋放。那麼這種實現方式的原理是什麼呢?我剖析問題時,喜歡剖析到問題的根上去,毫不糊塗的停留在表面。因爲程序在結束的時候,系統會自動析構全部的全局變量,實際上,系統也會析構全部類的靜態成員變量,就像這些靜態變量是全局變量同樣。咱們知道,靜態變量和全局變量在內存中,都是存儲在靜態存儲區的,因此在析構時,是同等對待的。

因爲此處使用了一個內部GC類,而該類的做用就是用來釋放資源,而這種使用技巧在C++中是普遍存在的,在後面的博客中,我會總結這一技巧,參見《C++中的RAII機制》

模式擴展

在實際項目中,一個模式不會像咱們這裏的代碼那樣簡單,只有在熟練了各類設計模式的特色,才能更好的在實際項目中進行運用。單例模式和工廠模式在實際項目中常常見到,兩種模式的組合,在項目中也是很常見的。因此,有必要總結一下兩種模式的結合使用。

一種產品,在一個工廠中進行生產,這是一個工廠模式的描述;而只須要一個工廠,就能夠生產一種產品,這是一個單例模式的描述。因此,在實際中,一種產品,咱們只須要一個工廠,此時,就須要工廠模式和單例模式的結合設計。因爲單例模式提供對外一個全局的訪問點,因此,咱們就須要使用簡單工廠模式中那樣的方法,定義一個標識,用來標識要建立的是哪個單件。因爲模擬代碼較多,在文章最後,提供下載連接。

總結

爲了寫這篇文章,本身調查了不少方面的資料,因爲網上的資料在各方面都有不少的瑕疵,質量參次不齊,對我也形成了必定的誤導。而這篇文章,有我本身的理解,若有錯誤,請你們指正。

因爲該文對設計模式的總結,我認爲比網上80%的都全面,但願對你們有用。在實際的開發中,並不會用到單例模式的這麼多種,每一種設計模式,都應該在最適合的場合下使用,在往後的項目中,應作到有地放矢,而不能爲了使用設計模式而使用設計模式。

相關文章
相關標籤/搜索