当前位置: 首页 > news >正文

设计模式---观察者模式

观察者模式(Observer Pattern)是行为型设计模式的核心成员,其核心思想是定义对象间的一对多依赖关系——当“被观察者”(主题)的状态发生变化时,所有依赖它的“观察者”对象会自动收到通知并执行更新操作。这种模式的本质是解耦主题与观察者,让两者可以独立演化,同时保证状态变化的“广播式”传递。在C++程序设计中,观察者模式广泛应用于GUI事件处理、监控系统、日志订阅、消息通知等场景,是构建松耦合系统的关键工具。

一、观察者模式的核心定义与设计意图

在软件设计中,经常会遇到“一个对象变化导致多个对象需要同步更新”的场景(例如:气象站数据变化时,手机APP、显示屏、报警器需同时更新)。若直接在主题中硬编码所有观察者的更新逻辑,会导致强耦合——新增观察者需修改主题代码,违反“开闭原则”。

观察者模式的设计意图正是解决这一问题:

  1. 分离“状态持有”(主题)与“状态响应”(观察者)的职责,符合“单一职责原则”;
  2. 主题仅依赖观察者的抽象接口,新增观察者无需修改主题代码,符合“开闭原则”;
  3. 支持“广播通信”,主题状态变化时,所有注册的观察者都会被通知,无需手动触发。

二、观察者模式的核心角色

观察者模式包含4个核心角色(部分场景可扩展第5个角色),各角色职责明确,通过抽象接口实现解耦。

核心角色定义

角色名称职责描述
Subject(主题/被观察者)抽象基类,定义观察者的注册、注销、通知接口;维护一个观察者列表。
ConcreteSubject(具体主题)继承Subject,维护实际业务状态;状态变化时,调用Subject的notify方法通知观察者。
Observer(观察者)抽象基类,定义观察者的“更新接口”(如update),供主题通知时调用。
ConcreteObserver(具体观察者)继承Observer,实现update方法;可持有ConcreteSubject的引用,用于获取最新状态。
Event(事件对象,可选)封装主题的状态变化细节(如“温度从25℃升至30℃”),避免update参数过多。

在这里插入图片描述

三、C++基础实现:从抽象到具体

C++中实现观察者模式的核心是基于抽象基类定义接口,通过“组合”让主题持有观察者列表,并在状态变化时遍历通知。以下以“气象站-显示器/报警器”为例,实现完整流程。

1. 步骤1:定义Event与抽象基类

首先定义WeatherEvent封装状态变化细节,再定义SubjectObserver抽象基类:

#include <vector>
#include <memory>  // 智能指针,避免内存泄漏
#include <iostream>
#include <string>// 1. 事件对象:封装气象站状态变化细节
struct WeatherEvent {std::string time;    // 时间戳float temperature;   // 温度(℃)float humidity;      // 湿度(%)// 构造函数:简化事件创建WeatherEvent(const std::string& t, float temp, float hum) : time(t), temperature(temp), humidity(hum) {}
};// 2. 观察者抽象基类:定义更新接口
class Observer {
public:virtual ~Observer() = default;  // 虚析构,确保子类析构正常调用// 纯虚函数:接收事件通知(推模型)virtual void update(const WeatherEvent& event) = 0;
};// 3. 主题抽象基类:定义注册、注销、通知接口
class Subject {
public:virtual ~Subject() = default;// 注册观察者virtual void addObserver(std::shared_ptr<Observer> observer) = 0;// 注销观察者virtual void removeObserver(std::shared_ptr<Observer> observer) = 0;// 通知所有观察者virtual void notifyObservers(const WeatherEvent& event) = 0;
};

2. 步骤2:实现具体主题(气象站)

ConcreteSubjectWeatherStation)维护实际状态,状态更新时触发通知:

// 具体主题:气象站
class WeatherStation : public Subject {
private:// 观察者列表:使用shared_ptr自动管理内存,避免野指针std::vector<std::shared_ptr<Observer>> observers_;// 气象站当前状态(可选:也可直接通过Event传递)float current_temp_;float current_hum_;public:// 注册观察者void addObserver(std::shared_ptr<Observer> observer) override {observers_.push_back(observer);std::cout << "新增观察者,当前总数:" << observers_.size() << std::endl;}// 注销观察者(遍历查找并删除)void removeObserver(std::shared_ptr<Observer> observer) override {for (auto it = observers_.begin(); it != observers_.end(); ++it) {if (*it == observer) {observers_.erase(it);std::cout << "移除观察者,当前总数:" << observers_.size() << std::endl;break;}}}// 通知所有观察者(推模型:主动传递Event)void notifyObservers(const WeatherEvent& event) override {// 遍历所有观察者,调用update方法for (auto& observer : observers_) {observer->update(event);}}// 业务逻辑:更新气象数据并触发通知void updateWeatherData(const std::string& time, float temp, float hum) {current_temp_ = temp;current_hum_ = hum;std::cout << "\n气象站更新数据:" << time << " 温度:" << temp << "℃ 湿度:" << hum << "%" << std::endl;// 创建事件并通知观察者WeatherEvent event(time, temp, hum);notifyObservers(event);}
};

3. 步骤3:实现具体观察者

ConcreteObserverPhoneDisplayAlarmSystem)实现update方法,响应状态变化:

// 具体观察者1:手机显示屏
class PhoneDisplay : public Observer {
private:std::string device_name_;  // 设备名称(如“小明的手机”)public:PhoneDisplay(const std::string& name) : device_name_(name) {}// 实现update:更新显示屏内容void update(const WeatherEvent& event) override {std::cout << "[" << device_name_ << "] 收到气象通知:" << std::endl;std::cout << "  时间:" << event.time << std::endl;std::cout << "  温度:" << event.temperature << "℃" << std::endl;std::cout << "  湿度:" << event.humidity << "%" << std::endl;}
};// 具体观察者2:高温报警器
class AlarmSystem : public Observer {
private:float temp_threshold_;  // 温度阈值(超过则报警)public:AlarmSystem(float threshold) : temp_threshold_(threshold) {}// 实现update:超过阈值则报警void update(const WeatherEvent& event) override {if (event.temperature > temp_threshold_) {std::cout << "[高温报警器] 警报!当前温度 " << event.temperature << "℃ 超过阈值 " << temp_threshold_ << "℃!" << std::endl;} else {std::cout << "[高温报警器] 状态正常,当前温度 " << event.temperature << "℃" << std::endl;}}
};

4. 步骤4:测试程序

int main() {// 1. 创建主题(气象站)std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();// 2. 创建观察者std::shared_ptr<Observer> phone1 = std::make_shared<PhoneDisplay>("小明的手机");std::shared_ptr<Observer> phone2 = std::make_shared<PhoneDisplay>("小红的手机");std::shared_ptr<Observer> alarm = std::make_shared<AlarmSystem>(30.0f);  // 30℃阈值// 3. 注册观察者station->addObserver(phone1);station->addObserver(phone2);station->addObserver(alarm);// 4. 模拟气象数据更新station->updateWeatherData("2024-05-20 08:00", 25.5f, 60.0f);station->updateWeatherData("2024-05-20 12:00", 32.0f, 45.0f);  // 超过报警阈值// 5. 注销观察者(小红的手机)station->removeObserver(phone2);station->updateWeatherData("2024-05-20 14:00", 31.5f, 42.0f);return 0;
}

5. 输出结果

新增观察者,当前总数:1
新增观察者,当前总数:2
新增观察者,当前总数:3气象站更新数据:2024-05-20 08:00 温度:25.5℃ 湿度:60%
[小明的手机] 收到气象通知:时间:2024-05-20 08:00温度:25.5℃湿度:60%
[小红的手机] 收到气象通知:时间:2024-05-20 08:00温度:25.5℃湿度:60%
[高温报警器] 状态正常,当前温度 25.5℃气象站更新数据:2024-05-20 12:00 温度:32℃ 湿度:45%
[小明的手机] 收到气象通知:时间:2024-05-20 12:00温度:32℃湿度:45%
[小红的手机] 收到气象通知:时间:2024-05-20 12:00温度:32℃湿度:45%
[高温报警器] 警报!当前温度 32℃ 超过阈值 30℃!
移除观察者,当前总数:2气象站更新数据:2024-05-20 14:00 温度:31.5℃ 湿度:42%
[小明的手机] 收到气象通知:时间:2024-05-20 14:00温度:31.5℃湿度:42%
[高温报警器] 警报!当前温度 31.5℃ 超过阈值 30℃!

四、关键变体:推模型 vs 拉模型

观察者模式的通知方式分为两种,核心区别在于状态数据的传递方式,需根据业务场景选择。

1. 推模型(Push Model)

  • 定义:主题主动将完整的状态数据(如WeatherEvent)推送给观察者,观察者无需主动获取。
  • 优点:观察者无需依赖主题的具体类型,仅通过Event即可获取所需数据,耦合度低;
  • 缺点:若观察者仅需部分数据,会造成数据冗余;新增数据字段需修改Event定义。
  • 适用场景:观察者需要的数据集固定、数据量小(如上述气象站案例)。

2. 拉模型(Pull Model)

  • 定义:主题仅通知“状态已变化”,观察者需主动从主题中“拉取”所需数据(需持有主题引用)。
  • 优点:观察者可按需拉取数据,避免冗余;新增数据字段无需修改Event
  • 缺点:观察者需依赖主题的具体类型(需知道WeatherStationgetTemperature()方法),耦合度高;
  • 实现修改:将Observer::update的参数改为Subject*,观察者通过主题指针拉取数据:
    // 拉模型的Observer接口
    class Observer {
    public:virtual ~Observer() = default;// 参数改为Subject*,观察者主动拉取数据virtual void update(Subject* subject) = 0;
    };// 拉模型的PhoneDisplay::update实现
    void PhoneDisplay::update(Subject* subject) override {// 强制转换为具体主题(需确保类型正确,有耦合风险)WeatherStation* station = dynamic_cast<WeatherStation*>(subject);if (station) {std::cout << "[" << device_name_ << "] 拉取数据:" << std::endl;std::cout << "  温度:" << station->getCurrentTemp() << "℃" << std::endl;  // 需主题提供getter}
    }
    

五、C++实战问题与解决方案

基础实现中存在3个典型问题,若不解决会导致程序崩溃或内存泄漏,需重点关注。

1. 问题1:遍历通知时的迭代器失效

场景:若观察者在update中调用removeObserver(如“收到通知后自动注销”),会导致vector的迭代器失效(erase后迭代器指向非法位置)。
解决方案

  • 方案1:使用std::list存储观察者(list::erase后仅当前迭代器失效,其他迭代器仍有效);
  • 方案2:遍历前拷贝观察者列表,基于拷贝列表通知(原列表修改不影响遍历):
    void WeatherStation::notifyObservers(const WeatherEvent& event) override {// 拷贝列表,避免遍历中修改原列表导致失效std::vector<std::shared_ptr<Observer>> temp_observers = observers_;for (auto& observer : temp_observers) {observer->update(event);}
    }
    

2. 问题2:多线程下的线程安全

场景:若主题在主线程更新状态,观察者在子线程执行update,并发操作观察者列表(add/remove/notify)会导致竞态条件(如列表元素被同时读写)。
解决方案:使用std::mutex加锁,保护观察者列表的所有操作:

#include <mutex>class WeatherStation : public Subject {
private:std::vector<std::shared_ptr<Observer>> observers_;std::mutex mutex_;  // 互斥锁,保护列表操作public:void addObserver(std::shared_ptr<Observer> observer) override {std::lock_guard<std::mutex> lock(mutex_);  // 自动加锁/解锁observers_.push_back(observer);}void removeObserver(std::shared_ptr<Observer> observer) override {std::lock_guard<std::mutex> lock(mutex_);// 查找并删除...}void notifyObservers(const WeatherEvent& event) override {std::lock_guard<std::mutex> lock(mutex_);// 通知...}
};

3. 问题3:观察者销毁未注销导致内存泄漏

场景:若观察者被销毁但未从主题中注销,主题的observers_仍持有其shared_ptr,导致观察者内存无法释放(shared_ptr引用计数不为0)。
解决方案

  • 方案1:观察者析构时自动注销(需主题提供removeObserver,且观察者持有主题引用):
    class PhoneDisplay : public Observer {
    private:std::shared_ptr<Subject> subject_;  // 持有主题引用public:PhoneDisplay(const std::string& name, std::shared_ptr<Subject> subject) : device_name_(name), subject_(subject) {subject_->addObserver(shared_from_this());  // 构造时注册}~PhoneDisplay() override {// 析构时自动注销subject_->removeObserver(shared_from_this());}
    };
    
  • 方案2:主题使用std::weak_ptr存储观察者(weak_ptr不增加引用计数,观察者销毁后自动失效):
    // 主题列表改为weak_ptr
    std::vector<std::weak_ptr<Observer>> observers_;void WeatherStation::notifyObservers(const WeatherEvent& event) override {std::lock_guard<std::mutex> lock(mutex_);for (auto it = observers_.begin(); it != observers_.end();) {// 尝试锁定weak_ptr,判断观察者是否存活if (auto observer = it->lock()) {observer->update(event);++it;} else {// 观察者已销毁,从列表中移除it = observers_.erase(it);}}
    }
    

六、高级实现的简化与优化

C++11引入的std::function和模板可大幅简化观察者模式代码,避免重复定义ConcreteObserver类。

1. 技巧1:使用std::function作为观察者回调

std::function可封装lambda、函数指针、成员函数,让观察者无需继承Observer基类,灵活性更高:

#include <functional>// 简化的主题:观察者列表为std::function
class SimpleWeatherStation {
private:using ObserverCallback = std::function<void(const WeatherEvent&)>;std::vector<ObserverCallback> callbacks_;std::mutex mutex_;public:// 注册观察者:传入回调函数void registerObserver(ObserverCallback callback) {std::lock_guard<std::mutex> lock(mutex_);callbacks_.push_back(callback);}// 通知观察者:调用所有回调void notify(const WeatherEvent& event) {std::lock_guard<std::mutex> lock(mutex_);for (auto& callback : callbacks_) {callback(event);}}// 业务逻辑:更新数据void updateData(const std::string& time, float temp, float hum) {WeatherEvent event(time, temp, hum);notify(event);}
};// 测试:直接用lambda作为观察者
int main() {SimpleWeatherStation station;// 注册观察者1:lambda(手机显示)station.registerObserver([](const WeatherEvent& e) {std::cout << "[lambda手机] 温度:" << e.temperature << "℃" << std::endl;});// 注册观察者2:绑定成员函数(报警器)AlarmSystem alarm(30.0f);station.registerObserver(std::bind(&AlarmSystem::update, &alarm, std::placeholders::_1));// 模拟数据更新station.updateData("2024-05-20 16:00", 33.0f, 40.0f);return 0;
}

2. 技巧2:模板化主题,支持任意事件类型

通过模板让主题支持不同的Event类型(如WeatherEventLogEvent),避免为每种事件定义单独的主题类:

template <typename EventType>
class GenericSubject {
private:using Callback = std::function<void(const EventType&)>;std::vector<Callback> callbacks_;std::mutex mutex_;public:void registerCallback(Callback callback) {std::lock_guard<std::mutex> lock(mutex_);callbacks_.push_back(callback);}void notify(const EventType& event) {std::lock_guard<std::mutex> lock(mutex_);for (auto& cb : callbacks_) {cb(event);}}
};// 使用:支持WeatherEvent和LogEvent
struct LogEvent {std::string level;std::string content;
};int main() {// 气象主题(Event为WeatherEvent)GenericSubject<WeatherEvent> weather_subject;// 日志主题(Event为LogEvent)GenericSubject<LogEvent> log_subject;return 0;
}

七、观察者模式的优缺点与适用场景

1. 优点

  • 解耦主题与观察者:主题仅依赖抽象接口,观察者可独立新增/删除;
  • 符合开闭原则:新增观察者无需修改主题代码;
  • 支持广播通信:主题状态变化时,所有观察者自动收到通知。

2. 缺点

  • 通知效率低:若观察者数量多,同步通知会阻塞主题线程;
  • 循环依赖风险:若观察者与主题互相引用(如A观察B,B观察A),可能导致死锁;
  • 状态变化原因不透明:观察者仅知道“状态变了”,但不知道“为什么变”。

3. 适用场景

  • 一个对象变化需同步更新多个对象,且对象数量不确定(如GUI按钮点击,多个控件响应);
  • 需解耦“生产者”(主题)与“消费者”(观察者),让两者独立演化(如日志系统:日志生产者 vs 文件/控制台消费者);
  • 需实现“广播式”通知(如监控系统:服务器状态变化,所有监控面板更新)。

八、易混淆模式对比:观察者 vs 发布-订阅(Pub/Sub)

很多开发者将两者混淆,实则核心区别在于是否存在中间件

对比维度观察者模式(Observer)发布-订阅模式(Pub/Sub)
耦合度主题与观察者直接依赖(通过抽象接口)发布者与订阅者完全解耦(无直接依赖)
中间件无(主题直接通知观察者)有(事件总线/消息队列,转发消息)
通信方式同步(默认)/异步异步(通常基于消息队列)
适用场景单机、进程内通信(如GUI、本地监控)分布式系统、跨进程通信(如微服务通知)

例如:“微信公众号”是典型的Pub/Sub——作者(发布者)发布文章到微信平台(事件总线),用户(订阅者)从平台接收消息,作者与用户无直接依赖;而“本地气象站-显示器”是观察者模式——气象站直接通知显示器,无中间件。


观察者模式是C++中构建松耦合系统的核心工具,其核心在于通过抽象接口解耦主题与观察者,并实现状态变化的自动广播。

  1. 核心角色:Subject(注册/注销/通知)、Observer(update)、ConcreteSubject/ConcreteObserver(业务实现);
  2. 通知模型:推模型(主动传数据,低耦合) vs 拉模型(按需拉数据,高耦合);
  3. 实战问题:迭代器失效(拷贝列表)、线程安全(加锁)、内存泄漏(自动注销/weak_ptr);
  4. 高级优化:std::function简化回调、模板支持多事件类型;
  5. 模式边界:与Pub/Sub的区别(中间件、耦合度)。

在实际项目中,需根据业务场景选择实现方式:简单场景用基础抽象类,复杂场景用std::function+模板,分布式场景则考虑Pub/Sub模式(如使用RabbitMQ、Kafka等消息队列)。

http://www.dtcms.com/a/494706.html

相关文章:

  • 【软考备考】 高并发场景如何做负载均衡知识点四
  • LOFAR物理频谱特征提取及实现
  • excel拼接数据库
  • 23ICPC杭州vp补题
  • 做网站不难吧长兴网站建设
  • Kafka、ActiveMQ、RabbitMQ、RocketMQ 对比
  • Unity中UI背景的高斯模糊
  • Avalonia 的命令基类和通知基类备份
  • 分布式和微服务的区别是什么?
  • windows10 安装 WSL2 及 ubuntu 24.04,Ubuntu中安装CUDA
  • 全链路智能运维中的多模态数据融合与语义对齐技术
  • 【DevOps】基于Nexus部署内网pypi代理镜像仓库操作手册
  • 微服务核心
  • 网站倒计时如何做自己的影视网站
  • 【DevOps】基于Nexus部署内网ubuntu 2204系统APT代理镜像仓库操作手册
  • 【开题答辩实录分享】以《开题报告 智能家居控制平台的构建》为例进行答辩实录分享
  • 建设论坛网站视频稿定设计官网入口
  • 利用R绘制箱线图
  • 【架构相关】tsconfig.json 与 tsconfig.node.json、tsconfig.app.json 的关系和作用
  • 烟台seo网站推广电商网站 手续
  • GLM-4.1V-Thinking vLLM部署调用
  • 从“生物进化”到算法优化:遗传算法的5个核心阶段
  • C++复习(1)
  • 云原生与分布式架构的完美融合:从理论到生产实践
  • 学习Python 03
  • Python中子类对父类方法的继承与改写
  • 深度学习之yolov3
  • 大型营销型网站建设网站做个seo要多少钱
  • 广州南建站时间dz网站建设教程
  • 【征文计划】Rokid 语音指令开发教程 【包含工程源码 和体验包APK】