• C++设计模式之观察者模式


    1.组件协作模式

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

    • Template Method
    • Startegy
    • Observer / Event

    2.动机(Motivation)

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

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

    3.代码案例

    当一个文件分割器需要显示一个进度条的时候一般代码如下:

    A依赖B

    • 如果编译A,则先编译B,这里的依赖指的是编译时的依赖
    //在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();
    	}
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    //分割进度条任务,通过传入的参数来处理
    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++) {
    			//...
    
    			if (m_progressBar != nullptr) {
    				m_progressBar->setValue((i + 1)/m_fileNumber); //更新进度条,依赖进度条类
    			}
    		}
    	}
    };
    
    
    • 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

    问题在于这个进度条是实现细节,如果什么时候需要别个显示方式。那代码整体就又需要改了,这对就不方便代码复用了。

    这里违背了依赖倒置原则(DIP)

    • 依赖倒置原则(DIP)
      高层模块(稳定)不应依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
      抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
    class IProgress {
    public:
    	virtual void DoProgress(float value) = 0;
    	virtual ~IProgress() {}
    };
    
    class FileSplitter
    {
    	string m_filePath;
    	int m_fileNumber;
    
    	//ProgressBar* m_progressBar;		//具体的通知控件
    	IProgress* m_iprogressBar;		//抽象通知机制
    
    public:
    	FileSplitter(const string& filePath, int fileNumber,
    		IProgress* iprogressBar) :
    		m_filePath(filePath),
    		m_fileNumber(fileNumber) ,
    		m_iprogressBar(iprogressBar)
    		{ }
    		
    	void split() {
    		//1.读取文件
    
    		//2.分批次向小文件中写入
    		for (int i = 0; i < m_fileNumber; i++) {
    			//...
    			
    			if (m_iprogressBar !=nullptr)
    			{
    					float progressValue = m_fileNumber;
    					progressValue = (i + 1) / progressValue;
    					m_iprogressBar ->DoProgress(progressValue);//FileSplitter不会耦合一个界面类了,以后这里是windows的,也可以是linux的,这里不依赖进度条类了
    			}
    			//m_iprogressBar  == nullptr则没有通知机制
    		}
    	}
    };
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • c++不推荐使用多继承,但是有一种继承是可以的:有一个主的继承类,其他的都是接口(或者抽象基类)。
    • 接口在C++里面就是抽象基类,继承一个接口
    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());
    
    		FileSplitter splitter(filePath, number, this);
    
    		splitter.split();
    	}
    	virtual void DoProgress(float value)
    	{
    		progressBar->setValue(value);
    	}
    	
    	
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    进一步优化

    • 上面这种模式只支持一种方式的消息机制,如果吧FileSplitter中的消息变为列表就能支持多种消息了。
      多个观察者IProgress,ConsoloNotifier
    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());
    
    		ConsoloNotifier cn;
    
    		FileSplitter splitter(filePath, number);
    
    		splitter.addIpgrogress(this);//订阅通知
    		splitter.addIprogress(&cn);
    		splitter.split();
    	}
    
    	virtual	void DoProgress(float value) {
    		progressBar->setValue(value);
    	}
    };
    
    
    class ConsoloNotifier :public IProgress {
    public:
    	virtual void DoProgress(float value) {
    		cout << "...";
    	}
    };
    
    
    • 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
    • 32
    • 33
    class IProgress {
    public:
    	virtual void DoProgress(float value) = 0;
    	virtual ~IProgress() {}
    };
    
    class FileSplitter
    {
    	string m_filePath;
    	int m_fileNumber;
    
    	//ProgressBar* m_progressBar;		//具体的通知控件
    	List<IProgress*> m_iprogressList;		//抽象通知机制,支持多消息机制
    
    public:
    	FileSplitter(const string& filePath, int fileNumber,
    		m_iprogress* iprogress) :
    		m_filePath(filePath),
    		m_fileNumber(fileNumber) {
    
    	}
    
    	void addIprogress(IProgress *ipgrogress) {
    		m_iprogressList.add(ipgrogress);
    	}
    
    	void removeIprogress(IProgress *ipgrogress) {
    		m_iprogressList.remove(ipgrogress);
    	}
    
    	void split() {
    		//1.读取文件
    
    		//2.分批次向小文件中写入
    		for (int i = 0; i < m_fileNumber; i++) {
    			//...
    			float progressValue = m_fileNumber;
    			progressValue = (i + 1) / progressValue;
    			onProgress(progressValue);
    		}
    	}
    
    protected:
    	void onProgress(float value) {
    		List<Iprogress>::interator itor = m_iprogressList.begin();
    		while (itor != m_iprogressList.end()) {
    			(*itor)->DoProgress(value);		//更新进度条
    			itor++;
    		}
    	}
    };
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    4.模式定义

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

    5.结构

    红色是稳定的部分(是系统中去依赖的部分),蓝色是变化的部分(是为了支持一对多的变化)

    • observer相当于IProgress,Update相当于DoProgress,Attach相当于addProgress,Detach相当于removeProgress,Notify()相当于OnProgress()。ConcreteSubject相当于FileSplitter,ConcreteObserver相当于MainForm。

    在这里插入图片描述

    6.要点总结

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

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

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

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

  • 相关阅读:
    400电话呼叫中心-佳音通讯400电话
    BIOS 如何确定引导扇区的位置
    前端跨界面之间的通信解决方案
    Android 屏幕适配
    【腾讯云云上实验室-向量数据库】腾讯云VectorDB:深度学习场景下的新一代数据存储方案
    基于粒子(Points)模拟雨雪天气效果
    JDK8升级JDK11最全实践干货来了
    java计算机毕业设计智慧校园实习岗位需求对接网络平台源程序+mysql+系统+lw文档+远程调试
    Gitlab部署管理
    Kafka 核心源码解读【三】--Controller模块
  • 原文地址:https://blog.csdn.net/u011436427/article/details/126573955