C++设计模式_05_Observer 观察者模式

news2024/10/7 10:13:59

接上篇,本篇将会介绍C++设计模式中的Observer 观察者模式,和前2篇模板方法Template MethodStrategy 策略模式一样,仍属于“组件协作”模式。Observer 在某些领域也叫做 Event

文章目录

  • 1. 动机( Motivation)
  • 2. 代码演示Observer 观察者模式
    • 2.1 常用处理方法
      • 2.1.1 MainForm1.cpp
      • 2.1.2 FileSplitter1.cpp
    • 2.2 Observer 观察者模式
      • 2.2.1 FileSplitter2.cpp
      • 2.2.2 MainForm2.cpp
  • 3. 模式定义
  • 4. 结构( Structure)
  • 5. 要点总结

1. 动机( Motivation)

  • 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

2. 代码演示Observer 观察者模式

假设以下的场景需求:做一个文件的分割器。虽然现在文件分割器使用的比较少,但是在之前是使用很广泛的,因为当时还是一个3寸盘的时代,经常需要将大文件拷走,就需要将大的文件分隔为多个文件拷贝携带走。
下面是一个伪码,只会展示主干部分。

2.1 常用处理方法

首先需要一个界面,mainform就是一个windows界面,父类为Form,主要有两个控件txtFilePath(大文件的全路径)和txtFileNumber(希望分割的文件个数)(此处给出的是后期修改后的代码)。
Button1_Click函数中收集用户输入的2个参数信息,传递给FileSplitter splitter,splitter调用split()

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

	}
};

2.1.2 FileSplitter1.cpp

FileSplitter中放文件变量,m_filePath负责文件路径,m_fileNumber负责文件个数,通过构造器给这些成员变量赋值。

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

	}
};

上面代码就实现了在Button1_Click时的文件的分割功能。

假设有一个用户需求,希望进行文件分割时,如果文件特别大,需要分割很长时间,这个时候需要提供一个进度条,进行进度展示。
最朴素的想法就是在界面中提供一个ProgressBar* progressBar,并且在FileSplitter中提供方法,代码结果如上。

但是上面的实现方式是否违背了某一设计原则呢?即违背依赖倒置原则(DIP)

  • 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
  • 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。

上面实现中,一般我们所讲的依赖就是编译式依赖(除非明确提出运行式依赖),例如A依赖B也就是A编译时B必须存
FileSplitter中ProgressBar* progressBar就产生了编译式依赖,这个ProgressBar* progressBar就是依赖倒置原则(DIP)中讲到的实现细节(为什么说它是实现细节呢?因为进度的显示方式可能会有变化,这就带了实现细节层面变更的困扰)

2.2 Observer 观察者模式

需要对上面的代码进行重构分析,可以看到ProgressBar* progressBar实际扮演的是一个通知,可以使用一种抽象的方式来表达一个通知而不需要具体的控件表达通知。

2.2.1 FileSplitter2.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++;
		}
	}
};

2.2.2 MainForm2.cpp

C++中支持多继承,一般不推荐使用多继承的方式,可能会导致复杂的耦合性问题,但是C++推荐一种多继承的形式就是一个是主继承类(如public Form),其他是接口或者抽象基类(public IProgress)。

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 << ".";
	}
};

3. 模式定义

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

4. 结构( Structure)

在这里插入图片描述

上图是《设计模式》GoF中定义的Observer 观察者模式的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。

在这里插入图片描述

GoF的设计模式中建议将addIProgress,removeIProgress,onProgress放到父类中,让FileSplitter去继承父类。而此处我们的框架是将其直接写到FileSplitter中,不管是否提出Subject都是观察者模式,本博文重构的代码就没有提出Subject,其实是将Subject和ConcreteSubject合二为一。

5. 要点总结

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

代码中随便添加观察者,但是addIProgress,removeIProgress,onProgress保持复用性不变

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

onProgress(progressValue);//发送通知不知道谁是观察者,针对通知机制抽象通知

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

splitter.addIProgress(this); //订阅通知splitter.addIProgress(&cn); //订阅通知

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

Observer 观察者模式与模板方法Template Method是一样的常用。例如java中的listener就是观察者模式、C#中的Event也是观察者模式

Observer模式需要多思考,最关键的是抽象的通知依赖关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Thonny安装教程

软件简介 Thonny —— 一个面向初学者的 Python IDE Thonny 由爱沙尼亚的 Tartu 大学开发&#xff0c;它采用了不同的方法&#xff0c;因为它的调试器是专为学习和教学编程而设计的。 下载&安装Thonny 下载地址 打开安装包&#xff0c;进入安装界面&#xff0c;一路Ne…

【每日一题】445. 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数字都不会以零开头。 示例1&#xff1a; 输入&#xff1a;l1 [7,2,4,3], l2 [5,6,4] 输…

电池电动汽车的健康状态 SOH 和充电状态 SOC 估计

完整程序请查看博主主页置顶博客&#xff08;专享优惠&#xff09; 主要内容&#xff1a; 健康状态 SOH采用平均加权最小二乘法&#xff08;AWTLS&#xff09;进行估计&#xff0c;并对比了加权最小二乘 &#xff08;WLS&#xff09;、总最小二乘法&#xff08;TLS&#xff0…

SQL7 查找年龄大于24岁的用户信息

描述 题目&#xff1a;现在运营想要针对24岁以上的用户开展分析&#xff0c;请你取出满足条件的设备ID、性别、年龄、学校。 用户信息表&#xff1a;user_profile iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20…

GE IS215UCVGM06A IS215UCVGH1A VMIVME-7666-111000燃机模块

数据采集和监测&#xff1a;燃机模块通常具有广泛的数据采集功能&#xff0c;用于监测燃机的性能参数&#xff0c;如温度、压力、振动等。这些数据有助于运营人员实时监测燃机状态。 控制功能&#xff1a;这些模块通常包括控制逻辑和算法&#xff0c;用于管理燃机的启停、负荷…

山东大学图书馆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著

山东大学图书馆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著

2023年中国光伏行业研究报告

第一章 行业概况 1.1 定义 光伏行业&#xff0c;也称为太阳能光伏行业&#xff0c;是一个专注于利用光伏技术将太阳能转化为电能的领域。该行业涵盖了太阳能电池的制造、光伏系统的设计、安装和维护&#xff0c;以及电能的销售和供应。光伏技术的核心是光伏效应&#xff0c;通…

这家酒店,管理方式太out了!换个风格后绝了!

在酒店业&#xff0c;我们追求的不仅仅是舒适和奢华&#xff0c;还包括客人的安全与健康。然而&#xff0c;火灾风险是无论在何处都不可忽视的潜在威胁。每年&#xff0c;全球范围内数以千计的火灾事件发生&#xff0c;不仅造成了巨大的财产损失&#xff0c;还可能威胁到人们的…

mysql查所有下级

//向下递归包含本级 WITH RECURSIVE cte AS (SELECT * FROM live_usr_config WHERE supid"lNy5ZNI3WZ3QXSopb0mdr"UNION ALLSELECT d.* FROM live_usr_config d INNER JOIN cte ON d.supid cte.subid ) SELECT * FROM cte;//向下递归包含本级并限制次数为下两层 W…

Mathpix替代者|科研人必备公式识别插件|latexocr安装教程

首先是安装好python、然后直接用命令调用 文章目录 1、安装步骤2、使用步骤 1、安装步骤 也可以安装精简版的python 准备 Python 环境&#xff0c;安装python的步骤可以查看我的上一篇博文&#xff1a; Anaconda最新版2023安装教程Spyder安装教程 确保你的计算机已经安装了 P…

大数据课程L7——网站流量项目的操作步骤

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解网站流量项目的Spark与HBase整合; ⚪ 掌握网站流量项目的实时流业务处理; 一、 Spark 与 HBase 整合基础 1. 实现步骤: 1. 启动 IDEA。 2. 创建 Maven 工程,骨架选择 quickstar…

2023/09/12 qtc++

实现一个图形类(Shape) &#xff0c;包含受保护成员属性:周长、面积&#xff0c; 公共成员函数:特殊成员函数书写 定义一个圆形类(Circle) &#xff0c;继承自图形类&#xff0c;包含私有属性:半径 公共成员函数:特殊成员函数…

使用Langchain+GPT+向量数据库chromadb 来创建文档对话机器人

使用LangchainGPT向量数据库chromadb 来创建文档对话机器人 一.效果图如下&#xff1a; 二.安装包 pip install langchainpip install chromadbpip install unstructuredpip install jieba三.代码如下 #!/usr/bin/python # -*- coding: UTF-8 -*-import os # 导入os模块&…

59-代码随想录--数组--螺旋矩阵

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给定一个正整数 n&#xff0c;生成一个包含 1 到 n^2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的正方形矩阵。 示例: 输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ] 模拟顺时针…

电路电子技术1--关联参考方向及功率的计算

1.判断 电流由元件的低点位端流向高电位端的参考方向称为关联参考方向。() 考点&#xff1a;电流、电压的参考方向 解释&#xff1a;在一些复杂的电路中&#xff0c;往往不能预先确定某段电路上的电流、电压的实际方向&#xff0c;所以引进了 “关联参考方向”。为了能够解决问…

网工内推 | 运营商技术支持,数通基础扎实,最高17k

01 新华三技术有限公司 招聘岗位&#xff1a;运营商技术支持工程师 职责描述&#xff1a; 1、负责新华三产品产品和方案在运营商客户的日常运维和技术支持&#xff1b; 2、为运营商客户提供网上问题处理、业务变更支持&#xff1b; 3、负责对应运营商客户日常维系&#xff0…

RabbitMQ基础概念-02

RabbitMQ是基于AMQP协议开发的一个MQ产品&#xff0c; 首先我们以Web管理页面为 入口&#xff0c;来了解下RabbitMQ的一些基础概念&#xff0c;这样我们后续才好针对这些基础概念 进行编程实战。 可以参照下图来理解RabbitMQ当中的基础概念&#xff1a; 虚拟主机 virtual hos…

绝热量热法反应热测试过程中的温度和压力自动跟踪控制解决方案

摘要&#xff1a;现有的ARC加速量热仪普遍存在单热电偶温差测量误差大造成绝热效果不好&#xff0c;以及样品球较大壁厚造成热惰性因子较大&#xff0c;都使得ARC测量精度不高。为此本文提出了技术改进解决方案&#xff0c;一是采用多只热电偶组成的温差热电堆进行温差测量&…

Dynamic-TP入门初探

背景 在使用线程池的过程中&#xff0c;会出现一些痛点&#xff1a; 代码中创建了一个线程池&#xff0c;但是不知道那几个核心参数设置多少比较合适。凭经验设置参数值&#xff0c;上线后发现需要调整&#xff0c;改代码重新发布服务&#xff0c;非常麻烦。线程池相对开发人…

[N0wayback 2023春节红包题] happyGame python反编译

这个反编译的比较深 一&#xff0c;从附件的图标看是python打包的exe文件&#xff0c;先用pyinstxtractor.py 解包 生成的文件在main.exe_extracted目录下&#xff0c;在这里边找到main 二&#xff0c;把main改名为pyc然后加上头 这个头从包里找一个带头的pyc文件&#xff…