C++实用技巧之 --- 观察者模式详解

C++实用技巧之 — 观察者模式详解

目录

  • C++实用技巧之 --- 观察者模式详解
    • 一、系统学习前的思考
    • 二、观察者模式详解
      • 1. 模式的定义
      • 2. 主要角色
      • 3. 模式的结构
      • 4. 实现步骤
      • 5. 优点
      • 6. 缺点
      • 7. 实际应用
        • 7.1 代码实现
        • 7.2 说明
        • 7.3 高级主题
        • 7.4 优点总结
        • 7.5 缺点总结
        • 7.6 应用原则
        • 7.7 相关设计模式
        • 7.8 解决常见问题
        • 7.9 总结

一、系统学习前的思考

  • 观察者模式被认为是一种行为设计模式。
    它通常用于实现“发布-订阅”机制,这样多个观察者可以订阅某个主题的对象,并在该主题发生变化时自动得到通知。听起来很实用,尤其是在构建事件驱动的系统时。
  • 那我应该从哪里开始呢?
    或许我应该先查一下观察者模式的基本概念和结构。根据一些资料,观察者模式包括两个主要角色:观察者(Observer)和主题(Subject)。主题维护一个观察者列表,并在状态变化时通知所有观察者。这似乎与发布-订阅模式类似,但更面向对象。
    我觉得理解观察者模式的关键在于如何定义观察者的接口,以及主题如何维护和通知这些观察者。可能需要使用接口或抽象类来定义观察者应该实现的方法。
  • 举个例子
    假设我有一个气象站(Subject),它会发布温度、湿度等数据。多个天气预报应用(Observers)订阅这个气象站的数据。每当气象站更新数据时,所有订阅的应用都会收到通知,更新自己的显示。
    那么,在C++中如何实现呢?我觉得可以定义一个Observer接口,其中包含一个update方法,用于接收主题的通知。然后,主题类会维护一个观察者的列表(Observer对象的集合)。
    让我尝试写出一些伪代码:
class Observer {
public:virtual void update(float temp, float humidity, float pressure) = 0;
};class WeatherData : public Subject {
private:float temperature;float humidity;float pressure;std::vector<Observer*> observers;public:void registerObserver(Observer* o) {observers.push_back(o);}void removeObserver(Observer* o) {// 从observers中删除o,通常需要编写一个循环来实现}void notifyObservers() {for (Observer* o : observers) {o->update(temperature, humidity, pressure);}}void measurementsChanged() {notifyObservers();}// 其他方法如setMeasurements
};class CurrentConditionsDisplay : public Observer {
private:float temperature;float humidity;float pressure;public:void update(float temp, float humi, float press) override {temperature = temp;humidity = humi;pressure = press;display();}void display() {// 显示当前天气状况}
};
  • 看起来收获很大,但当我实际尝试在C++中实现时,有些地方让我困惑。
    例如:
    如何正确管理观察者的生命周期,避免内存泄漏?
    是否需要使用智能指针代替原始指针?
    另外,实现Subject接口时,如何有效地维护和更新观察者列表。
  • 在安全性方面
    C++的观察者模式是否容易出现竞态条件(racecondition)?
    如果有多个线程同时订阅或取消订阅,是否会引发问题?
    是否需要使用互斥锁来保护访问?
  • 此外,我发现有些资料中的Subject使用了抽象类,而有些则使用了接口。这在C++中可能会不同,因为我需要显式地定义接口,可能需要使用纯虚基类。
  • 我还需要极大地考虑性能问题。如果一个主题有成千上万的观察者,每次通知都会遍历整个列表,这是否会导致性能瓶颈?是否有一种高效的通知机制?
  • 哦,还有迭代器的使用。在通知所有观察者时,如何防止迭代过程中观察者列表发生变化导致迭代器失效?有没有办法安全地遍历观察者列表?
  • 这些问题让我意识到,尽管观察者模式的基本概念不难理解,但在实际实现和实时应用中,还有很多细节需要考虑。此外,我需要确保代码的可扩展性,以便未来可以轻松地添加新的观察者类型或修改通知机制。
  • 我需要查阅更多的资料,查看如何在实际项目中实现观察者模式,看看专家们是如何处理这些细节的。同时,尝试在实际项目中应用,通过实验来理解可能遇到的问题和最佳实践。

总结:

1. 理解观察者模式的基本结构,包括Observer和Subject。
2. 研究如何在C++中正确实现Observer接口,确保多态性。
3. 学习如何管理观察者的注册和注销,避免内存泄漏。
4. 考虑线程安全和性能优化,尤其是在高并发场景下。
5. 通过实际项目和实验,深入理解模式的实际应用和潜在问题。

二、观察者模式详解

1. 模式的定义

观察者模式(Observer Pattern)属于行为设计模式,旨在定义对象之间的一对多依赖关系,使得当一个对象(主题/被观察者)状态改变时,所有依赖于它的对象(观察者)自动通知并更新。

2. 主要角色

• 主题(Subject/被观察者) :维护观察者列表,提供注册和注销观察者的方法,并在状态变化时通知所有注册的观察者。
• 观察者(Observer) :定义一个更新接口,当接收到主题的通知时,执行相应的更新逻辑。

3. 模式的结构

• EventManager(事件管理器)(可选) :用于管理多个主题和观察者之间的关系,简化注册和注销过程。

4. 实现步骤

• 定义观察者接口 :创建一个基类或抽象类,定义update方法。
• 创建具体观察者类 :继承观察者接口,并实现update方法,定义具体的更新逻辑。
• 实现主题类 :维护一个观察者列表,提供registerObserver、removeObserver和notifyObservers方法。
• 在主题中添加状态变化处理逻辑 :当主题的状态变化时,调用notifyObservers以通知所有观察者。

5. 优点

• 松耦合 :主题和观察者之间保持松耦合,减少相互依赖,提升系统的可维护性和可扩展性。
• 广播通信 :改变一个主题的状态,可以通知所有注册的观察者,实现广播通信。
• 易于扩展 :新增观察者时,无需修改现有代码,遵循开闭原则。

6. 缺点

• 潜在的性能问题 :通知大量观察者时,可能导致性能瓶颈。
• 复杂的依赖关系管理 :观察者的注册和注销需要谨慎处理,避免内存泄漏或访问已删除的观察者。

7. 实际应用

7.1 代码实现

以下示例展示了如何在C++中实现观察者模式:

#include <vector>
#include <memory>
#include <string>class Observer {
public:virtual void update(float temp, float humidity, float pressure) = 0;virtual ~Observer() = default;
};class DisplayElement {
public:virtual void display() = 0;virtual ~DisplayElement() = default;
};class WeatherData {
private:float temperature;float humidity;float pressure;std::vector<std::shared_ptr<Observer>> observers;void notifyObservers() {for (const auto& observer : observers) {observer->update(temperature, humidity, pressure);}}public:// 注册观察者void registerObserver(std::shared_ptr<Observer> observer) {observers.push_back(observer);}// 注销观察者void removeObserver(std::shared_ptr<Observer> observer) {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float t, float h, float p) {temperature = t;humidity = h;pressure = p;measurementsChanged();}void measurementsChanged() {notifyObservers();}
};class CurrentConditionsDisplay : public Observer, public DisplayElement {
private:float temperature;float humidity;float pressure;std::string name;public:CurrentConditionsDisplay(const std::string& name) : name(name) {}~CurrentConditionsDisplay() override = default;void update(float temp, float humi, float press) override {temperature = temp;humidity = humi;pressure = press;display();}void display() override {std::cout << "Current conditions(s" << name << "): "<< temperature << "F degrees and " << humidity << "% humidity\n";}
};int main() {std::shared_ptr<WeatherData> weatherData = std::make_shared<WeatherData>();std::shared_ptr<CurrentConditionsDisplay> currentDisplay1 = std::make_shared<CurrentConditionsDisplay>("Display 001");std::shared_ptr<CurrentConditionsDisplay> currentDisplay2 = std::make_shared<CurrentConditionsDisplay>("Display 002");weatherData->registerObserver(currentDisplay1);weatherData->registerObserver(currentDisplay2);weatherData->setMeasurements(80, 65, 30.4f);weatherData->setMeasurements(82, 70, 29.9f);weatherData->removeObserver(currentDisplay2);weatherData->setMeasurements(78, 90, 29.2f);return 0;
}
7.2 说明

• Observer接口 :定义了纯虚函数update,所有观察者必须实现此方法。
• DisplayElement接口 :定义了display方法,用于显示当前状态。
• WeatherData类 :主题类,维护了 observrs 向量,用来存储所有注册的观察者。提供registerObserver和removeObserver方法用于注册和注销观察者。setMeasurements方法更新传感器数据后,调用measurementsChanged,后者通知所有观察者。
• CurrentConditionsDisplay类 :具体观察者,实现update方法以更新显示数据,并通过display方法呈现当前状况。

7.3 高级主题

• 使用智能指针管理生命周期 :通过std::shared_ptr管理观察者的生命周期,避免内存泄漏。
• 线程安全 :在多线程环境下访问主题或观察者时,应使用互斥锁(如std::mutex)来保护共享资源。
• 事件管理器(可选) :对于复杂的系统,可以使用事件管理器来统一管理主题和观察者的关系,简化注册过程。

7.4 优点总结

• 松耦合设计 :主题和观测者之间无需知道彼此的具体实现,只需通过接口通信,系统更易于维护和扩展。
• 广播式通信 :一个主题的状态变化可以同时通知多个观察者。
• 动态管理观察者 :可以在运行时动态注册或注销观察者,提升系统的灵活性。

7.5 缺点总结

• 性能问题 :通知大量观察者可能带来性能开销,特别是在高频率更新的场景中。
• 潜在的竞态条件 :在多线程环境下,若不采取同步措施,可能会导致不一致状态。

7.6 应用原则

• 合适场景 :当系统中存在一对多的依赖关系,且希望自动通知变化时适用。
• 避免过度使用 :虽然观察者模式很灵活,但滥用可能导致系统复杂性增加。

7.7 相关设计模式

• 发布-订阅模式(Publish-Subscribe) :观察者模式的扩展,允许主题和观察者间更松散的耦合,支持频道或主题过滤。
• 模板方法模式(Template Method) :与观察者模式结合,可以在主题中定义算法的钩子方法(hook),允许观察者自定义部分行为。
• 迭代器模式(Iterator) :用于遍历观察者列表,避免暴露主题的内部细节。

7.8 解决常见问题

• 内存泄漏 :使用智能指针管理观察者的生命周期,避免显式内存管理带来的风险。
• 线程安全 :在主题通知和注册/注销操作时,使用互斥锁或者其他同步机制保护共享资源的安全。
• 动态扩展 :允许在运行时通过反射或工厂模式动态加载新的观察者,提升系统的灵活性。

7.9 总结

观察者模式在C++中是一种强大且灵活的设计模式,适用于处理对象间的动态一对多依赖关系。通过合理设计接口和使用智能指针、线程同步机制,可以有效克服其潜在缺点,构建高效且易于维护的系统。熟悉这些概念和实现细节,将有助于开发人员在实际项目中更好地应用观察者模式,提升代码的质量和系统的可扩展性。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全-防御 第一次作业(由于防火墙只成功启动了一次未补截图)

防火墙安全策略课堂实验报告 一、拓扑 本实验拓扑包含预启动设备、DMZ区域&#xff08;含OA Server和Web Server&#xff09;、防火墙&#xff08;FW1&#xff09;、Trust区域&#xff08;含办公区PC和生产区PC&#xff09;等。具体IP地址及连接关系如给定拓扑图所示&#xf…

Vue.js 与低代码开发:如何实现快速应用构建

在当今数字化时代&#xff0c;企业对应用开发的效率要求越来越高。传统开发模式往往耗时费力&#xff0c;难以满足快速变化的市场需求。而 Vue.js 与低代码开发的结合&#xff0c;为快速构建应用提供了新的解决方案&#xff0c;让企业能够更敏捷地响应市场变化&#xff0c;抢占…

第39周:猫狗识别 2(Tensorflow实战第九周)

目录 前言 一、前期工作 1.1 设置GPU 1.2 导入数据 输出 二、数据预处理 2.1 加载数据 2.2 再次检查数据 2.3 配置数据集 2.4 可视化数据 三、构建VGG-16网络 3.1 VGG-16网络介绍 3.2 搭建VGG-16模型 四、编译 五、训练模型 5.1 上次程序的主要Bug 5.2 修改版…

朝天椒USB服务器解决前置机U盾虚拟机远程连接

本文探讨朝天椒USB服务器用Usb Over Network技术&#xff0c;解决前置机虚拟化部署后U盾的远程连接问题。 在金融、电信等关键行业&#xff0c;后台核心处理系统承担着至关重要的业务数据交互职责。为保障系统安全&#xff0c;这些单位要求企业通过前置机与他们的内网进行数据…

《刚刚问世》系列初窥篇-Java+Playwright自动化测试-23- 操作鼠标拖拽 - 番外篇(详细教程)

拉票 亲爱的小伙伴们或者童鞋们&#xff0c;喜欢宏哥文章的&#xff0c;请动动你们发财小手&#xff0c;给我投投票票 。 祝2025小伙伴们工作顺利&#xff0c;家庭和睦&#xff0c;心想事成&#xff0c;财源滚滚&#xff01; 我的票还有7票&#xff0c;互票的朋友私信给我。 投…

教程 | 从零部署到业务融合:DeepSeek R1 私有化部署实战指南

文章目录 1. 什么是 DeepSeek R1&#xff1f;a. 主要介绍a. 版本区别 2. 部署资源要求a. 硬件资源要求 3. 本地安装DeepSeek-R1a. 为什么选择本地部署&#xff1f;b. 部署工具对比c. 演示环境配置d. Ollama安装流程 4. 可视化工具a. 工具对比b. Open-WebUI部署 5. AI API应用a.…

学习总结2.14

深搜将题目分配&#xff0c;如果是两个题目&#xff0c;就可以出现左左&#xff0c;左右&#xff0c;右左&#xff0c;右右四种时间分配&#xff0c;再在其中找最小值&#xff0c;即是两脑共同处理的最小值 #include <stdio.h> int s[4]; int sum0; int brain[25][25]; …

Qt Creator 5.0.2 (Community)用久了突然变得很卡

目录 1.现象 2.解决方案 1.现象 很久没有用Qt Creator开发项目了&#xff0c;刚刚结束的项目又是用VS2019开发的&#xff1b;这两天刚好有时间去学习一下Qt&#xff0c;刚好要用Qt Creator&#xff0c;结果一打开就没反应&#xff0c;主界面显示出来要好几分钟&#xff0c;最…

DeepSeek的深度解析:由来、研发过程、公司背景、优势、劣势与总结

DeepSeek的由来 DeepSeek&#xff0c;中文名“深度求索”&#xff0c;是一个在人工智能领域崭露头角的创新项目。其英文名“DeepSeek”由“深思”&#xff08;Deep&#xff09;与“探索”&#xff08;Seek&#xff09;组合而成&#xff0c;寓意着凭借深度学习技术不断探索未知…

初阶c语言(练习题,猜随机数)

前言&#xff1a; 学习c语言&#xff0c;学习来源b站鹏哥&#xff0c;37天吧应该是 内容&#xff1a; 这集内容挺多&#xff0c;源代码放到文章最后 题目是&#xff0c;使用函数编写一个随机数&#xff0c;然后自己猜&#xff0c;猜随机数 这里囊括了很多的知识点&#xf…

w206基于Spring Boot的农商对接系统的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

Python PyCharm DeepSeek接入

Python PyCharm DeepSeek接入 创建API key 首先进入DeepSeek官网,https://www.deepseek.com/ 点击左侧“API Keys”,创建API key,输出名称为“AI” 点击“创建",将API key保存,复制在其它地方。 在PyCharm中下载Continue插件 安装 下载中 下载完成后,点击OK 配…

鸿蒙开发:了解@Builder装饰器

前言 本文代码案例基于Api13&#xff0c;温馨提示&#xff1a;内容相对来说比较简单&#xff0c;如果您已掌握&#xff0c;略过即可。 如果说一个页面中组件有很多&#xff0c;我们都统一写到build函数中&#xff0c;显而易见&#xff0c;会导致build函数代码非常冗余&#xff…

一文深入了解DeepSeek-R1:模型架构

本文深入探讨了 DeepSeek-R1 模型架构。让我们从输入到输出追踪 DeepSeek-R1 模型&#xff0c;以找到架构中的新发展和关键部分。DeepSeek-R1 基于 DeepSeek-V3-Base 模型架构。本文旨在涵盖其设计的所有重要方面。 &#x1f4dd; 1. 输入上下文长度 DeepSeek-R1的输入上下文长…

Linux进程管理

一、进程查看 1、进程 进程 process 计算机执行任务的最小单位 2、进程查看 ps auxa&#xff1a;all u&#xff1a;user x&#xff1a;所有终端 所有用户所有终端的所有进程 COMMAND&#xff1a; 进程名称 USER&#xff1a; 启动进程的用户&…

(5/100)每日小游戏平台系列

新增一个数字迷宫游戏&#xff01; 数字迷宫游戏是一款基于迷宫探索的益智游戏。玩家从迷宫的起点出发&#xff0c;必须根据迷宫中的数字指示&#xff0c;选择正确的方向&#xff0c;通过迷宫最终到达终点。游戏的目标是尽快找到并到达终点。 游戏规则 起点与终点&#xff1a;…

latex二重闭合积分显示

latex二重闭合积分显示 环境 texlive2024texstdio4.8.6 解决 添加宏包 \usepackage{esint} % 在导言区加载宏包符号 \oiint测试 documentclass[12pt]{article} \usepackage{esint} % 在导言区加载宏包 \title{Hello} \author{Houor}\begin{document}\maketitleHello, \L…

WebP2P+自研回音消除:视频通话SDK嵌入式EasyRTC构建高交互性音视频应用

随着移动互联网时代的到来&#xff0c;手机端的扬声器大多采用外置设计&#xff0c;且音量较大。在这种情况下&#xff0c;扬声器播放的声音更容易被麦克风捕捉&#xff0c;从而导致回声问题显著加剧。这种设计虽然方便用户在免提模式下使用&#xff0c;但也带来了更复杂的音频…

二分查找sql时间盲注,布尔盲注

目录 一&#xff1a;基础知识引导 数据库&#xff1a;information_schema里面记录着数据库的所有元信息 二&#xff0c;布尔盲注&#xff0c;时间盲注 &#xff08;1&#xff09;布尔盲注案例&#xff08;以sqli-labs第八关为例&#xff09;&#xff1a; &#xff08;2&am…

机器学习 - 理论和定理

在机器学习中&#xff0c;有一些非常有名的理论或定理&#xff0c;对理解机器学习的内在特性非常有帮助。本文列出机器学习中常用的理论和定理&#xff0c;并举出对应的举例子加以深化理解&#xff0c;有些理论比较抽象&#xff0c;我们可以先记录下来&#xff0c;慢慢啃&#…