观察者模式

观察者模式

动机

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

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

代码举例

原始需求:实现一个分割器,能够将指定路径txtFilePath的文件分割为指定份数txtFileNumber的文件。在分割器中增加一个进度条来实时显示文件的分割进度,此时不参考设计模式,则会在两个代码中添加ProgressBar*类型的成员变量,用于实现进度条。
思考:

  • 那么随着项目推进,显示分割进度的方式若发生变化,那么就需要在源代码上继续将ProgressBar*类型的成员变量修改为相应的控件类型,显然违背设计原则。

FileSplitter类文件

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类文件

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

	}
};

很显然,这样的方法违背设计原则:
依赖导致原则 —— 高层模块不应依赖底层模块,二者皆应该依赖抽象;抽象不应依赖实现细节,实现细节应该依赖于抽象。 在实现进度通知过程中,字段ProgressBar* m_progressBar是实现进度条通知的控件,是一种具体的实现细节。在FileSplitter类使用了成员变量ProgressBar,产生了编译时依赖。实现细节非常容易变化,会使得本设计不合理。

面向对象设计代码

首先明确,在本设计中字段ProgressBar* m_progressBar是具体的实现控件,扮演着通知的角色,违反依赖倒置原则。为了符合依赖倒置原则,这个通知可以使用抽象的方式来表达,而不需使用具体某个控件来表示。

FileSplitter类文件

//专门用于显示通知的Observer抽象层
class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};


//ConcreateObserver类,根据通知刷新状态
class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};

class FileSplitter
{
	string m_filePath;
	int m_fileNumber;
        //ProgressBar* m_progressBar;  具体的通知控件
        //IProgress* m_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++;
		}
	}
};

MainForm类文件

class MainForm : public Form, public IProgress
{
	TextBox* txtFilePath;
	TextBox* txtFileNumber;

public:
	void Button1_Click(){

		string filePath = txtFilePath->getText();
		int number = atoi(txtFileNumber->getText().c_str());

		ConsoleNotifier cn1;
		ConsoleNotifier cn2;
		FileSplitter splitter(filePath, number);
		
		//多个观察者订阅通知
		splitter.addIProgress(&cn1); //订阅通知
		splitter.addIProgress(&cn2); //订阅通知

		splitter.split();

		splitter.removeIProgress(&cn1);
		splitter.removeIProgress(&cn2);
		
	}
};


注意:在上面的代码中使用了c++的多继承机制。由于在多继承会带来一系列的耦合性问题等,c++语言中并不推荐多继承的使用。但是推荐一个主继承类,其他为接口类的多继承的使用方法。如上,主继承类为Form,接口类为IProgress。

此时,使得整体代码结构符合了依赖倒置原则。

在这里,最大的改进是不再使用通知控件,而是使用通知使用机制,这样就避免了依赖,符合了依赖倒置原则。

同时,这里为了符合多个观察者,链表来支持多个观察者机制。

模式定义

GOF对于观察者模式的定义如下:

定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

下面看类图

image

Observer是稳定部分,是观察者侧的抽象类。在Observer抽象类中定义的函数Update(),用于更新观察者的状态,是纯虚函数。在本示例中IProgress就是Observer抽象类,而ConsoleNotifier则是ConcreateObserver,重写Update()函数,根据观察到的值更新状态。

在本例中,FileSplitter是Subject和ConcreateSubject的结合体,其中其中定义的函数onProgress就是Notify(),会为每个订阅通知的观察者推送通知并刷新状态。在上图中,是将Subject和ConcreateSubject两个模块分开,Subject为抽象类,在Subject中定义纯虚函数Attach、Detach和Notify函数,在ConcreateSubject类中进行重写并实现。Attach用于观察者订阅通知,Detach用于观察者关闭订阅通知,Notify为每个订阅通知的观察者推送通知。

要点总结

  • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使两者之间的依赖关系达致松耦合

  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播

  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知

  • Observer模式是基于事件的UI框架种非常常用的设计模式,也是MVC模式的一个重要组成部分

posted on   hxh_space  阅读(30)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2020-04-10 offer-first
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示