【设计模式】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模式的一个重要组成部分。

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