爲了更好地理解面向對象的思想,我需要更好地理解一下24種常用的設計模式,因此計劃好好理解一下這些設計模式並把自己對於這種模式的一些想法和實現的代碼記錄在博客上。
今天首先從最簡單的“單例模式”開始:
單例模式是創建型模式的一種,是24種設計模式中相對最簡單的一種,但是使用頻率還是比較高的。
書上對其的定義爲:確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
我們初學者乍一看,感覺這個定義下得還是比較難理解的,因此下面我來給出自己的理解方式:在我們實現一個系統的時候,有些對象我們並不希望在程序中隨便的創建(比如說查詢系統配置參數),原因有以下兩點,1、如果多次創建這個對象,而每次的創建都實現相同的功能,返回相同的結果,毫無疑問這樣的重複創建是一種浪費系統資源的行爲;2、如過兩個同時打開的對象返回的結果不相同,那麼就意味着程序出現不一致,可能是某個對象修改了數據,而另外一個對象向並沒有作出相應的改變。
因此在我們的實際開發過程中,一旦碰到了這樣的問題,我們可以應用“單例模式”來進行解決。
以類singleSchema爲例
解決方法:
1、因爲我們不允許隨意的new新的對象出來,所以我們的構造函數和析構函數要寫爲私有的。
2、雖然我們不允許類外new新的對象,但是在類內這種行爲是被允許的,定義一個私有的靜態指向自己類型(singleSchema)的指針,今後用來指向類內創建的對象(當然在沒有創建新的對象時指向nullptr)。
3、使用一個靜態函數來返回這個指針所指的對象。
基於以上幾點解決方案,我們可以寫出以下的頭文件:
/*
* class.h
*
* Created on: 2014-3-18
* Author: rookie
*/
#ifndef CLASS_H_
#define CLASS_H_
#include <iostream>
class singleSchema
{
private:
singleSchema();
virtual ~singleSchema();
static singleSchema* pSch;
int value;
public:
static singleSchema* getInstance(){
if(pSch == nullptr)
pSch = new singleSchema();
return pSch;
}
void printValue();
void setValue(int);
};
singleSchema::singleSchema()
{
this->value = 0;
}
singleSchema::~singleSchema()
{
}
void singleSchema::printValue()
{
std::cout<<this->value<<std::endl;
}
void singleSchema::setValue(int val)
{
this->value = val;
}
singleSchema* singleSchema::pSch = nullptr;
#endif /* CLASS_H_ */
當我們每次需要調用類的setValue(int)和printValue()函數時,首先調用getInstance()返回指向內部定義的對象的指針,再通過指針調用這兩個函數。
我們分析上面的代碼可知,在我們使用getInstance()函數的時候,我們先判斷了指向內部創建對象的指針是否爲空,由於我做了singleSchema* singleSchema::pSch = nullptr; 操作,所以會在類內創建對象,並且用指針指向它,那麼以後在調用這個函數的時候,就不再會創建新的對象了。主函數的測試內容如下:
#include <iostream>
#include "class.h"
using namespace std;
int main()
{
singleSchema* ptr1 = singleSchema::getInstance();
ptr1->printValue();
ptr1->setValue(20);
ptr1->printValue();
singleSchema* ptr2 = singleSchema::getInstance();
ptr2->printValue();
return 0;
}
如果運行上述代碼,出現的結果是0 20 20,可以知道無論我們在哪使用printValue()和setValue(int)函數,結果都不會改變的,看似我們的目的已經達到了!但是又有一個新的問題出現了,如果在多線程調用這兩個函數呢?
通過實驗得到的是,還是出現了結果不一致的情況!因爲在某個線程第一次調用getInstance()函數時,類的內部new了一個新的對象出來,可是就當在new新的對象的時候,另一個線程調用了getInstance()函數,檢測到的仍是指針爲空,那麼這時衝突就產生了。
因此出現了餓漢式單例類和懶漢式單例類,前者的實現方式是在類被加載時,先創建內部對象,這樣系統不論是否用到此對象,始終會佔用資源;後者認識第一次被訪問時創建對象,但是類要處理好多線程的調用問題。兩者都有利弊。
最後介紹一下單例模式的適用場景:
1、系統只需要一個實例對象
2、客戶調用實例,只允許使用一個公共訪問點,除此之外,不再允許其他途徑訪問。