注意:該觀察者模式與模板模式都很常用。百分之八十的面向對象的開發代碼結構都會用到觀察者模式
01 動機(Motivation)
在軟件構建過程中,我們需要爲某些對象建立一種“通知依賴關係” ——一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知。如果這樣的依賴關係過於緊密,將使軟件不能很好地抵禦變化。
使用面向對象技術,可以將這種依賴關係弱化,並形成一種穩定的依賴關係。從而實現軟件體系結構的松耦合。
02 模式定義
定義對象間的一種一對多(變化)的依賴關係,以便當一個對象(Subject)的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
——《設計模式》GoF
03 樣例
這是一個以前老版的文件分割器,就是以前盤小,需要將大文件分割成小的帶走。現在的需求是,在UI面加上切割進度條的需求。FileSplitter.cpp指程序庫代碼。MainForm.cpp指用戶界面。
僞代碼 07
FileSplitter1.cpp
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar;//編譯時依賴。
public:
FileSplitter(const string& filePath, int fileNumber, **ProgressBar* progressBar**) :
m_filePath(filePath),
m_fileNumber(fileNumber),
**m_progressBar(progressBar)**{
}
void split(){
//1.讀取大文件
//2.分批次向小文件中寫入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
MainForm1.cpp
//文件分割器
class MainForm : public Form
{
TextBox* txtFilePath;//需要分割文件的全路徑
TextBox* txtFileNumber;//需要分割的文件個數
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, **progressBar**);
splitter.split();
}
};
如上兩個代碼,違背了 依賴倒置原則,高層依賴於了低層。
如我們需要在文件傳輸的過程中需要顯示進度條,
一般剛剛畢業的程序員可能就會如上那麼做,在mainform文件和FileSplitter.cpp文件中分別設置變量
ProgressBar* progressBar和ProgressBar* m_progressBar,然後把變量m_progressBar放入到for循環之中。
那麼當我們不用這種進度條的方式顯示進度時,需求更改時,我們的高層程序FileSplitter.cpp就會被編譯時更改。
這個代碼這樣做,會帶給我們一個實現細節的困擾。實現細節是不斷變更的,這樣依賴不好。
僞代碼 08
FileSplitter2.cpp
很明顯在下面的代碼中不管MainForm.cpp怎麼改變,其實對下面的代碼都是沒有影響的,解決了緊耦合和依賴倒置的問題
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;//這是一個具體的通知控件
//IProgress* IProgress;//這是一個抽象通知機制,支持單一觀察者。
List<IProgress*> m_iprogressList; // 抽象通知機制,支持多個觀察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.讀取大文件
//2.分批次向小文件中寫入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//發送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){//可以設置成虛方法,供子類可以進行改寫
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() )
(*itor)->DoProgress(value); //更新進度條
itor++;
}
}
};
MainForm1.cpp
//觀察者1
class MainForm : public Form, public IProgress
//C++編程中 不建議使用多繼承,會造成緊耦合之類的問題,但是有種情況就是隻有一個主繼承,其他的都是9
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;//通知,這是用一個具體的控件來專門通知。沒必要這樣做
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //訂閱通知
splitter.addIProgress(&cn); //訂閱通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){//具體實現
progressBar->setValue(value);
}
};
//觀察者2
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
如今這個代碼,進度條通知,我們可以進行隨需求的更改,放置在不同的平臺,都與高層代碼FileSplitter2.cpp無關。
在UI界面對高層的抽象類進行具體實現,若要通知時,只需要高層虛函數來調用MainForm.cpp下層具體實現的文件
。
04 結構
紅色圈畫的是穩定的,藍色圈畫的是變化的。
05 要點總結
使用面向對象的抽象,Observer模式使得我們可以獨立地改變目標與觀察者,從而使二者之間的依賴關係達致松耦合。
目標發送通知時,無需指定觀察者,通知(可以攜帶通知信息作爲參數)會自動傳播。
觀察者自己決定是否需要訂閱通知,目標對象對此一無所知。//說的普通點,就是通知進度的代碼,不在分割的代碼裏面包含了。
Observer模式是基於事件的UI框架中非常常用的設計模式,也是MVC模式的一個重要組成部分。