C++设计模式_05_Observer 观察者模式
接上篇,本篇将会介绍C++设计模式中的Observer 观察者模式,和前2篇模板方法Template Method及Strategy 策略模式一样,仍属于“组件协作”模式。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. 要点总结
- 6. 其他参考博文
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合二为一。
个人结合结构对以上代码的理解:
IProgress的两个基类MainForm和ConsoleNotifier(虚函数多态,ConcreteObserver),通过FileSplitter splitter(ConcreteSubject)的addIProgress()方法都订阅了通知,在进行split()时,遍历ConcreteObserver,利用迭代器循环执行各自具体的DoProgress()。这样就实现多个ConcreteObserver观察同一ConcreteSubject,并且多个ConcreteObserver会根据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模式需要多思考,最关键的是抽象的通知依赖关系。
6. 其他参考博文
Observer 观察者模式
相关文章:
C++设计模式_05_Observer 观察者模式
接上篇,本篇将会介绍C设计模式中的Observer 观察者模式,和前2篇模板方法Template Method及Strategy 策略模式一样,仍属于“组件协作”模式。Observer 在某些领域也叫做 Event 。 文章目录 1. 动机( Motivation)2. 代码…...
github网站打不开,hosts文件配置
首先获取github官网的ip地址, 打开cmd,输入ping github.com 配置: #github 140.82.114.4 github.com 199.232.69.194 github.global.ssl.fastly.net 185.199.108.153 assets-cdn.github.com 185.199.110.153 assets-cdn.github.com 185.199…...
总结PCB设计的经验
一般PCB基本设计流程如下:前期准备->PCB结构设计->PCB布局->布线->布线优化和丝印->网络和DRC检查和结构检查->制版。: : 第一:前期准备。这包括准备元件库和原理图。“工欲善其事,必先利其器”,要做出一…...
HCIE-HCS规划设计搭建
1、相关术语 1、等价路由 等价路由(Equal-cost routing)是一种网络路由策略,用于在网络中选择多个具有相同路由度量(路由距离或成本)的最佳路径之一来转发数据流量。 当存在多个路径具有相同的路由度量时,…...
c语言输出杨辉三角
#include<stdio.h> int main() {int x 0; //表示杨辉三角的的大小int y 1;printf("请输入x的值: ");scanf("%d", &x);for (int i 0; i < x; i) {for (int j 0; j < i; j) {if (j 0 || i 0) {y 1;}else {y y * (i - j 1) / j;}pri…...
性能测试-持续测试及性能测试建设(22)
什么是持续测试? 持续测试定义为:在软件交付流水线中执行自动化测试的过程,目的是获得关于预发布软件业务风险的即时反馈。 完成持续测试,我们还是需要回到定义中,它有3个关键词:软件交付流水线、自动化测试、即时反馈。 首先,持续测试需要具备一条完整的流水线,其代表…...
嵌入式C 语言中的三块技术难点
C 语言在嵌入式学习中是必备的知识,甚至大部分操作系统都要围绕 C 语言进行,而其中有三块技术难点,几乎是公认级别的“难啃的硬骨头”。 今天就来带你将这三块硬骨头细细拆解开来,一定让你看明白了。 0x01 指针 指针是公认…...
【斗破年番】紫研新形象,萧炎终成翻海印,救援月媚,三宗决战
Hello,小伙伴们,我是小郑继续为大家深度解析斗破年番。 斗破苍穹年番动画更新了,小医仙帅气回归,萧炎紫妍成功进入山谷闭关苦修,美杜莎女王守护没多久,就因蛇人族求救离开。从官方公布的最新预告来看,萧炎紫…...
差分方程模型:国民总收入(GDP)的乘数-加速数模型
【背景知识-凯恩斯经济增长模型】 凯恩斯(John M.Keynes)建立了著名的国民经济增长模型。令Y表示国民总收入,C表示总消费,E为总支出,I表示投资,G为政府的投入(如基建等)。那么有 【6.1】 其中࿰…...
【C语言】指针和数组笔试题解析(1)
指针是C语言的灵魂,他的玩法多种多样,这篇文章带来指针的笔试题详解,可以帮助我们更好的理解与巩固指针的知识 目录 预备知识:题目:一维数组:二维数组: 题目比较多,但切记戒骄戒躁&a…...
Vue中组件的三种注册方式
组件的注册 1.全局注册: 在全局注册中,你需要确保在 Vue 根实例之前导入并注册组件。通常,你会在入口文件(例如 main.js)中执行这些操作。 // main.jsimport Vue from vue; import App from ./App.vue;// 导入全局组…...
docker 和k8s 入门
docker 和k8s 入门 本文是云原生的学习记录,可以参考以下文档 k8s https://www.yuque.com/leifengyang/oncloud 相关视频教程可参考如下 https://www.bilibili.com/video/BV13Q4y1C7hS?p2&vd_source0882f549dac54045384d4a921596e234 相对于公有云&#x…...
基于Yolov8的交通标志牌(TT100K)识别检测系统
1.Yolov8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的(SOTA)模型,它建立在先前YOLO成功基础上,并引入了新功能和改进,以进一步提升性能和灵活…...
使用Python编写一个多线程的12306抢票程序
国庆长假即将到来,大家纷纷计划着自己的旅行行程。然而,对于很多人来说,抢购火车票人们成了一个令人头疼的问题。12306网站的服务器经常因为流量高而崩溃,导致抢票变得越来越严重异常困难。 首先,让我们来了解一下1230…...
DT Paint Effects工具(三)
管 分支 使用细枝 叶 力 使用湍流 流动画 渲染全局参数 建造盆栽植物...
SpringBoot整合Mybatis
目录 (1)引入依赖 (2)编写Mapper接口 (3)编写Mapper映射文件 (4)编写yml配置文件 (5)编写测试类 (1)引入依赖 <dependency>…...
Java后端使用POST请求向mysql中插入Json数据的问题
1.后端请求正常 但数据表中value没有值 原因 json数据属性不符合spring解析格式,json属性名称的大写字母不符合spring要求 以下为为错误示范 1 Test 以大写字母开头, 2 tTest 小写字母开头,但是第二个字母是大写解决方案 实体类属性加上Jso…...
豆瓣图书评分数据的可视化分析
导语 豆瓣是一个提供图书、电影、音乐等文化产品的社区平台,用户可以在上面发表自己的评价和评论,形成一个丰富的文化数据库。本文将介绍如何使用爬虫技术获取豆瓣图书的评分数据,并进行可视化分析,探索不同类型、不同年代、不同…...
SpringBoot整合Easy-ES操作演示文档
文章目录 SpringBoot整合Easy-ES操作演示文档1 概述及特性1.1 官网1.2 主要特性 2 整合配置2.1 导入POM2.2 Yaml配置2.3 EsMapperScan 注解扫描2.4 配置Entity2.5 配置Mapper 3 基础操作3.1 批量保存3.2 数据更新3.3 数据删除3.4 组合查询3.5 高亮查询3.6 统计查询 4 整合异常4…...
IDEA控制台取消悬浮全局配置SpringBoot配置https
IDEA控制台取消悬浮 idea 全局配置 SpringBoot(Tomcat) 配置https,同时支持http 利用JDK生成证书 keytool -genkey -alias httpsserver -keyalg RSA -keysize 2048 -keystore server.p12 -validity 3650配置类 Configuration public class TomcatConfig {Value(&quo…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
