3.观察者模式(Observer)

组件协作模式

现代软件专业分工之后的第一个结果是 "框架与应用程序的划分","组件协作" 模式通过晚期绑定,来实现框架与应用程序直接的松耦合,是二者之间协作时常用的模式

典型模式

Template Method

Strategy

Observer /Event

动机(Motivation)

  • 在软件构建过程中,我们需要为某些对象建立一种通知依赖关系 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

假定场景:

当前做一个文件的分割器,将大的文件分割成多个文件。

首先有一个界面,MainForm 就是一个界面。

cpp 复制代码
class MainForm : public Form
{
	TextBox* txtFilePath; // 文件路径
	TextBox* txtFileNumber; // 希望分割的文件个数 

public:
	void Button1_Click(){
		// 接收用户传进来的两个信息
		string filePath = txtFilePath->getText();
		int number = atoi(txtFileNumber->getText().c_str());

		FileSplitter splitter(filePath, number);
		
		splitter.split(); // 调用split方法

	}
};


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);
		}

	}
};

当前文件如果处理特别大的文件,那么就需要给用户一个进度条,首先需要在界面 MainForm 中添加进度条控件 ProgressBar* progressBar;

在使用中 Button1_Click 传入当前进度条.最终在实际操作中 FileSplitter 函数 split 内部计算并设置进度条 m_progressBar->setValue(progressValue);

如下代码所示:

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(); // 调用split方法

	}
};

//
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;
			// 更新进度条
			if(nullpt != m_progressBar)
				m_progressBar->setValue(progressValue);
		}

	}
};

上述方法违背了依赖倒置设计原则:高层模块不能依赖底层模块,二者都应该依赖于抽象,抽象不能依赖于实现细节,实现细节应该依赖于抽象。

如果当前 它不是UI程序,而是控制台程序,那么 ProgressBar* m_progressBar;这行代码可能并不能适用于别的显示进度方式。

新的方式:

首先做一个抽象的通知

cpp 复制代码
class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};

然后将 FileSplitter 中的具体通知控件 ProgressBar* m_progressBar; 替换成抽线通知机制 IProgress* iprogress

cpp 复制代码
class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};


class FileSplitter
{
	string m_filePath;
	int m_fileNumber;

	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++;
		}
	}
};

然后在 UI 中进行多继承

class MainForm : public Form, public IProgress

并且实现:

cpp 复制代码
virtual void DoProgress(float value){
		progressBar->setValue(value);
	}

其实如果再添加一个控制台的程序,打印进度的,也好添加:

cpp 复制代码
class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};

然后 需要支持多个 List<IProgress*> m_iprogressList; // 抽象通知机制,

支持多个观察者。

完整代码如下:

cpp 复制代码
class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};


class FileSplitter
{
	string m_filePath;
	int m_fileNumber;

	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++;
		}
	}
};
///
class MainForm : public Form, public IProgress
{
	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);
	}
};

class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};

总结

  • 使用面向对象的抽象,Observer 模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
  • Observer 模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
相关推荐
烛.照1031 小时前
宝塔安装完redis 如何访问
linux·数据库·redis·缓存
未知陨落1 小时前
冯诺依曼系统及操作系统
linux·操作系统
纪伊路上盛名在1 小时前
ML基础-Jupyter notebook中的魔法命令
linux·服务器·人工智能·python·jupyter
躺不平的理查德2 小时前
Shell特殊位置变量以及常用内置变量总结
linux·运维·服务器
康王有点困2 小时前
(1)Linux高级命令简介
linux·运维·服务器
乙卯年QAQ2 小时前
【linux】linux缺少tar命令/-bash: tar:未找到命令
linux·运维·bash
向上的车轮2 小时前
OpenEuler学习笔记(十四):在OpenEuler上搭建.NET运行环境
linux·笔记·学习·.net
千航@abc3 小时前
vim如何解决‘’文件非法关闭后,遗留交换文件‘’的问题
linux·编辑器·vim
晚秋贰拾伍4 小时前
设计模式的艺术-观察者模式
运维·观察者模式·设计模式·运维开发
_Eden_5 小时前
Haproxy介绍及学习
linux·学习·haproxy