【設計模式】05 Observer/Event 觀察者模式(“組件協作”模式三)

注意:該觀察者模式與模板模式都很常用。百分之八十的面向對象的開發代碼結構都會用到觀察者模式

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模式的一個重要組成部分。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章