观察者模式详解与C++实现

news2025/4/19 23:49:01

1. 模式定义

观察者模式(Observer Pattern)是一种行为型设计模式,定义了对象间的一对多依赖关系。当一个对象(被观察者/主题)状态改变时,所有依赖它的对象(观察者)都会自动收到通知并更新。该模式广泛应用于事件处理系统、GUI组件交互和实时数据监控等场景。


2. 核心思想

解耦:将主题与观察者解耦,使它们可以独立变化
动态订阅:支持观察者随时加入/退出通知列表
自动传播:状态变化时自动触发通知机制


3. 模式结构

在这里插入图片描述

关键角色:
  • Subject(主题接口)

    • 维护观察者列表
    • 提供注册/注销方法
    • 定义通知方法notify()
  • ConcreteSubject(具体主题)

    • 存储具体状态数据
    • 状态改变时触发通知
  • Observer(观察者接口)

    • 定义更新接口update()
  • ConcreteObserver(具体观察者)

    • 实现具体的更新逻辑

4. C++实现示例(气象监测系统)

完整代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>  // 智能指针支持

// 主题接口 --------------------------------------------------
class WeatherStation; // 前向声明

class Observer {
public:
    virtual ~Observer() = default;
    // 更新方法(推模型:由主题推送数据)
    virtual void update(float temp, float humidity) = 0;
    // 更新方法(拉模型:观察者主动获取数据)
    // virtual void update(const WeatherStation& subject) = 0;
};

// 抽象主题 --------------------------------------------------
class Subject {
public:
    virtual ~Subject() = default;
    virtual void registerObserver(std::shared_ptr<Observer> o) = 0;
    virtual void removeObserver(std::shared_ptr<Observer> o) = 0;
    virtual void notifyObservers() = 0;
};

// 具体主题:气象站 -------------------------------------------
class WeatherStation : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers_;
    float temperature_ = 0.0f;
    float humidity_ = 0.0f;

public:
    // 注册观察者
    void registerObserver(std::shared_ptr<Observer> o) override {
        observers_.push_back(o);
        std::cout << "[系统] 新的观察者已注册\n";
    }

    // 移除观察者
    void removeObserver(std::shared_ptr<Observer> o) override {
        auto it = std::remove(observers_.begin(), observers_.end(), o);
        if(it != observers_.end()) {
            observers_.erase(it, observers_.end());
            std::cout << "[系统] 观察者已移除\n";
        }
    }

    // 通知所有观察者(推模型实现)
    void notifyObservers() override {
        std::cout << "[系统] 开始通知" << observers_.size() << "个观察者...\n";
        for(auto& observer : observers_) {
            observer->update(temperature_, humidity_);
        }
    }

    // 业务方法:更新气象数据
    void setMeasurements(float temp, float humidity) {
        this->temperature_ = temp;
        this->humidity_ = humidity;
        std::cout << "\n=== 气象站更新数据 ===" << std::endl;
        notifyObservers();
    }

    // 供拉模型使用的数据获取接口
    float getTemperature() const { return temperature_; }
    float getHumidity() const { return humidity_; }
};

// 具体观察者:手机APP ----------------------------------------
class MobileApp : public Observer {
private:
    std::string userName_;

public:
    explicit MobileApp(std::string name) : userName_(std::move(name)) {}

    // 实现更新接口(推模型)
    void update(float temp, float humidity) override {
        std::cout << "[手机APP] " << userName_ << "收到更新:"
                  << temp << "°C, " << humidity << "%\n";
        // 可以在此添加业务逻辑,如触发界面刷新
    }
};

// 具体观察者:户外大屏 ---------------------------------------
class OutdoorScreen : public Observer {
public:
    void update(float temp, float humidity) override {
        std::cout << "[户外大屏] 最新天气:"
                  << temp << "°C | 湿度 " << humidity << "%\n";
        // 实际项目可能包含图形渲染逻辑
    }
};

// 客户端使用 ------------------------------------------------
int main() {
    // 创建主题
    WeatherStation station;

    // 创建观察者(使用智能指针管理)
    auto user1 = std::make_shared<MobileApp>("张三");
    auto user2 = std::make_shared<MobileApp>("李四");
    auto screen = std::make_shared<OutdoorScreen>();

    // 注册观察者
    station.registerObserver(user1);
    station.registerObserver(user2);
    station.registerObserver(screen);

    // 模拟数据变化
    station.setMeasurements(25.5, 60);
    station.setMeasurements(23.0, 65);

    // 移除一个观察者
    station.removeObserver(user2);

    // 再次更新数据
    station.setMeasurements(20.0, 70);

    return 0;
}
代码注释解析
  1. 内存管理改进

    • 使用std::shared_ptr智能指针管理观察者对象
    • 防止原始指针可能导致的野指针问题
    std::vector<std::shared_ptr<Observer>> observers_;
    
  2. 推模型 vs 拉模型

    • 当前实现为推模型(直接传递数据)
    • 注释部分展示了拉模型的接口设计
    /* 拉模型实现示例
    void update(const WeatherStation& subject) override {
        float temp = subject.getTemperature();
        float humidity = subject.getHumidity();
        // 使用获取的数据...
    }
    */
    
  3. 观察者类型多样化

    • 实现不同观察者类型(MobileAppOutdoorScreen
    • 展示不同观察者的差异化处理逻辑
  4. 安全移除机制

    • 使用std::remove+erase组合安全删除元素
    • 避免迭代器失效问题
    auto it = std::remove(observers_.begin(), observers_.end(), o);
    if(it != observers_.end()) {
        observers_.erase(it, observers_.end());
    }
    

5. 模式优劣分析

优势:
  • 松耦合:主题无需知道观察者的具体实现
  • 动态订阅:运行时随时添加/移除观察者
  • 广播通信:支持一对多通知机制
劣势:
  • 更新顺序不可控:观察者接收通知的顺序不确定
  • 性能损耗:大量观察者时遍历效率低(可考虑异步优化)

6. 应用场景

  1. GUI事件处理
    • 按钮点击事件通知多个控件
  2. 实时监控系统
    • 传感器数据变化通知多个终端
  3. 发布-订阅系统
    • 消息中间件的核心设计模式
  4. 游戏开发
    • 角色状态变化触发多个UI更新

7. 高级扩展方向

  1. 异步通知

    • 使用线程池处理观察者更新
    // 伪代码示例
    void asyncNotify() {
        std::vector<std::future<void>> futures;
        for(auto& observer : observers_) {
            futures.push_back(std::async([&]{
                observer->update(...);
            }));
        }
    }
    
  2. 事件过滤

    • 添加事件类型参数,实现选择性通知
    enum class EventType { TEMP_CHANGE, HUMIDITY_CHANGE };
    virtual void update(EventType type, float value);
    
  3. 优先级队列

    • 使用优先队列管理观察者
    std::priority_queue<std::shared_ptr<Observer>> pq_;
    

8. 总结

观察者模式通过建立高效的发布-订阅机制,有效降低了系统组件间的耦合度。在实际应用中需要根据具体需求选择推/拉模型,并注意内存管理和线程安全等问题。

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

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

相关文章

UE5 关卡序列

文章目录 介绍创建一个关卡序列编辑动画添加一个物体编辑动画时间轴显示秒而不是帧时间轴跳转到一个确定的时间时间轴的显示范围更改关键帧的动画插值方式操作多个关键帧 播放动画 介绍 类似于Unity的Animation动画&#xff0c;可以用来录制场景中物体的动画 创建一个关卡序列…

AI测试用例生成平台

AI测试用例生成平台 项目背景技术栈业务描述项目展示项目重难点 项目背景 针对传统接口测试用例设计高度依赖人工经验、重复工作量大、覆盖场景有限等行业痛点&#xff0c;基于大语言模型技术实现接口测试用例智能生成系统。 技术栈 LangChain框架GLM-4模型Prompt Engineeri…

C#中扩展方法和钩子机制使用

1.扩展方法&#xff1a; 扩展方法允许向现有类型 “添加” 方法&#xff0c;而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法&#xff0c;但可以像实例方法一样进行调用。 使用场景&#xff1a; 1.当无法修改某个类的源代码&#…

YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12的网络结构图

文章目录 一、YOLOv5二、YOLOv6三、YOLOv7四、YOLOv8五、YOLOv9六、YOLOv10七、YOLOv11八、YOLOv12九、目标检测系列文章 本文将给出YOLO各版本&#xff08;YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12&#xff09;网络结构图的绘制方法及图。本文所展…

03 UV

04 Display工具栏_哔哩哔哩_bilibili 讲的很棒 ctrlMMB 移动点 s 打针 ss 批量打针

AIGC-几款本地生活服务智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…

C#/.NET/.NET Core拾遗补漏合集(25年4月更新)

前言 在这个快速发展的技术世界中&#xff0c;时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节&#xff0c;以帮助大家更全面地了解这些技术栈的特性和发展方向。 ✍C#/.NET/.N…

MySQL性能调优(三):MySQL中的系统库(简介、performance_schema)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 1.MySQL中的系统库1.1.系统库简介1.2.performance_schema1.2.1.什么是performance_schema1.2.2.performance_schema使用1.2.3.检查当前数据库版本是否支持1.2.4.performance_schema表的分类1.2.5.performanc…

印度zj游戏出海代投本土网盟广告核心优势

印度游戏出海代投本土网盟广告的核心优势包括&#xff1a; 本土化广告策略&#xff1a;针对印度市场的特点&#xff0c;定制本土化的广告策略&#xff0c;吸引更多印度用户的关注和参与。 深度了解印度市场&#xff1a;对印度文化、消费习惯、网络使用习惯等有深入了解&#x…

NO.97十六届蓝桥杯备战|数论板块-最大公约数和最小公倍数|欧几里得算法|秦九韶算法|小红的gcd(C++)

约数和倍数 如果a 除以b 没有余数&#xff0c;那么a 就是b 的倍数&#xff0c;b 就是a 的约数&#xff0c;记作b ∣ a 。 约数&#xff0c;也称因数。 最⼤公约数和最⼩公倍数 最⼤公约数Greatest Common Divisor&#xff0c;常缩写为gcd。 ⼀组整数的公约数&#xff0c;是…

《软件设计师》复习笔记(11.6)——系统转换、系统维护、系统评价

目录 一、遗留系统&#xff08;Legacy System&#xff09; 定义&#xff1a; 特点&#xff1a; 演化策略&#xff08;基于价值与技术评估&#xff09;&#xff1a; 高水平 - 低价值&#xff1a; 高水平 - 高价值&#xff1a; 低水平 - 低价值&#xff1a; 低水平 - 高价…

人像面部关键点检测

此工作为本人近期做人脸情绪识别&#xff0c;CBAM模块前是否能加人脸关键点检测而做的尝试。由于创新点不是在于检测点的标注&#xff0c;而是CBAM的改进&#xff0c;因此&#xff0c;只是借用了现成库Dilb与cv2进行。 首先&#xff0c;下载人脸关键点预测模型:Index of /file…

EDID结构

EDID DDC通讯中传输显示设备数据 VGA , DVI 的EDID由128字节组成&#xff0c;hdmi的EDID增加扩展块128字节。扩展快的内容主要是和音频属性相关的&#xff0c;DVI和vga没有音频&#xff0c;hdmi自带音频&#xff0c;扩展快数据规范按照cea-861x标准。 Edid为了让pc或其他的图像…

文件包含(详解)

文件包含漏洞是一种常见的Web安全漏洞&#xff0c;其核心在于应用程序未对用户控制的文件路径或文件名进行严格过滤&#xff0c;导致攻击者能够包含并执行任意文件&#xff08;包括本地或远程恶意文件&#xff09;。 1. 文件包含原理 动态文件包含机制 开发者使用动态包含函数…

《SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战》​

&#x1f31f; ​大家好&#xff0c;我是摘星&#xff01;​ &#x1f31f; 今天为大家带来的是Scheduled和Quartz对比分析&#xff1a; 新手常见困惑&#xff1a; 刚学SpringBoot时&#xff0c;我发现用Scheduled写定时任务特别简单。但当我看到同事在项目里用Quartz时&…

安装fvm可以让电脑同时管理多个版本的flutter、flutter常用命令、vscode连接模拟器

打开 PowerShellfvm安装 dart pub global activate fvm安装完成后&#xff0c;如果显示FVM无法识别&#xff0c;那么需要去添加环境变量path添加这个&#xff1a;C:\Users\Administrator\AppData\Local\Pub\Cache\bin 常用命令 fvm releases 查看用户可以装的flutter版本fvm l…

Kafka系列之:计算kafka集群topic占的存储大小

Kafka系列之:计算kafka集群topic占的存储大小 topic存储数据格式统计topic存储大小定时统计topic存储大小topic存储数据格式 单位是字节大小 size_bytes{directory="/data/datum/kafka/optics-all" } 782336计算topic存储大小脚本逻辑是: 计算指定目录或文件的大小…

智谱AI大模型免费开放:开启AI创作新时代

文章摘要&#xff1a;近日&#xff0c;国内领先的人工智能公司智谱AI宣布旗下多款大模型服务免费开放&#xff0c;这一举措标志着大模型技术正式迈入普惠阶段。本文将详细介绍智谱AI此次开放的GLM-4 等大模型&#xff0c;涵盖其主要功能、技术特点、使用步骤以及应用场景&#…

T1结构像+RS-fMRI影像处理过程记录(数据下载+Matlab工具箱+数据处理)

最近需要仿真研究T1结构像RS-fMRI影像融合处理输出目标坐标的路线可行性。就此机会记录下来。 为了完成验证目标处理&#xff0c;首先需要有数据&#xff0c;然后需要准备对应的处理平台和工具箱&#xff0c;进行一系列。那么开始记录~ 前言&#xff1a; 为了基于种子点的功能连…

【前端基础】--- HTML

个人主页  :  9ilk    专栏  :  前端基础 文章目录 &#x1f3e0; 初识HTML&#x1f3e0; HTML结构认识HTML标签HTML文件基本结构标签层次结构快速生成代码框架 &#x1f3e0; HTML常见标签注释标签标题标签 h1-h6段落标签 p换行标签 br格式化标签图片标签 img超链接标签…