原文地址:http://www.jellythink.com/archives/82ios
如今,無論開發一個多大的系統(至少我如今的部門是這樣的),都會帶一個日誌功能;在實際開發過程當中,會專門有一個日誌模塊,負責寫日誌,因爲在系統的任何地方,咱們都有可能要調用日誌模塊中的函數,進行寫日誌。那麼,如何構造一個日誌模塊的實例呢?難道,每次new一個日誌模塊實例,寫完日誌,再delete,不要告訴我你是這麼幹的。在C++中,能夠構造一個日誌模塊的全局變量,那麼在任何地方就均可以用了,是的,不錯。可是,我所在的開發部門的C++編碼規範是參照Google的編碼規範的。程序員
全局變量在項目中是能不用就不用的,它是一個定時炸彈,是一個不安全隱患,特別是在多線程程序中,會有不少的不可預測性;同時,使用全局變量,也不符合面向對象的封裝原則,因此,在純面向對象的語言Java和C#中,就沒有純粹的全局變量。那麼,如何完美的解決這個日誌問題,就須要引入設計模式中的單例模式。數據庫
何爲單例模式,在GOF的《設計模式:可複用面向對象軟件的基礎》中是這樣說的:保證一個類只有一個實例,並提供一個訪問它的全局訪問點。首先,須要保證一個類只有一個實例;在類中,要構造一個實例,就必須調用類的構造函數,如此,爲了防止在外部調用類的構造函數而構造實例,須要將構造函數的訪問權限標記爲protected或private;最後,須要提供要給全局訪問點,就須要在類中定義一個static函數,返回在類內部惟一構造的實例。意思很明白,使用UML類圖表示以下。編程
單例模式,單從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 ** 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%的都全面,但願對你們有用。在實際的開發中,並不會用到單例模式的這麼多種,每一種設計模式,都應該在最適合的場合下使用,在往後的項目中,應作到有地放矢,而不能爲了使用設計模式而使用設計模式。