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

设计模式(C++)详解——观察者模式(Observer)(1)

观察者模式(Observer)深度解析:从理论到C++实战

1. 背景与核心概念

1.1 设计模式的起源与发展

观察者模式是软件工程中最经典、最常用的设计模式之一,它的历史可以追溯到面向对象编程的早期阶段。让我们回顾一下它的发展历程:

时期主要贡献应用场景技术特点
1970sSmalltalk MVC框架GUI应用程序模型-视图分离
1994GoF《设计模式》事件处理系统标准化模式描述
2000s.NET事件委托桌面应用开发语言级支持
2010s响应式编程Web前端、移动端数据流驱动

观察者模式的核心思想源于现实世界中的订阅-发布机制。就像报纸订阅一样:

  • 出版社(主题)维护订阅者列表
  • 读者(观察者)可以订阅或取消订阅
  • 新报纸出版时,所有订阅者自动收到通知

1.2 模式定义与关键角色

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

让我们用Mermaid类图来理解各个角色的关系:

维护观察者列表
Subject
+attach(Observer* observer) : void
+detach(Observer* observer) : void
+notify() : void
ConcreteSubject
-state : int
+getState() : int
+setState(int state) : void
«interface»
Observer
+update(Subject* subject) : void
ConcreteObserver
-observerState : int
+update(Subject* subject) : void

关键角色解析

  • Subject(主题):维护观察者列表,提供添加、删除和通知观察者的接口
  • ConcreteSubject(具体主题):存储感兴趣的状态,状态改变时通知观察者
  • Observer(观察者):为所有具体观察者定义更新接口
  • ConcreteObserver(具体观察者):维护与主题状态一致的自身状态

1.3 MVC架构中的核心地位

观察者模式是MVC(Model-View-Controller)架构的核心机制:

通知更新
通知更新
通知更新
修改数据
Model 数据模型
View 视图1
View 视图2
View 视图3
Controller 控制器

在这种架构中:

  • Model 充当主题角色,维护业务数据和状态
  • View 充当观察者角色,监听Model的变化并更新显示
  • Controller 处理用户输入,修改Model状态

2. 设计意图与考量

2.1 核心设计目标

观察者模式的设计主要围绕以下几个目标:

1. 解耦性

  • 主题不需要知道观察者的具体类
  • 观察者可以独立变化和复用
  • 降低系统各部分之间的依赖

2. 动态关系

  • 运行时可以动态添加、删除观察者
  • 观察者关系可以随时改变
  • 支持灵活的订阅机制

3. 广播通信

  • 支持一对多的通信模式
  • 主题变化自动通知所有观察者
  • 避免手动维护更新逻辑

2.2 生命周期管理的挑战

观察者模式中最棘手的问题就是生命周期管理。考虑以下场景:

// 危险代码示例:悬空指针问题
Subject* subject = new Subject();
Observer* observer = new ConcreteObserver();subject->attach(observer);  // 主题持有观察者指针// 之后某个时刻...
delete observer;  // 观察者被销毁
// 但主题仍然持有已销毁对象的指针!subject->notify();  // 灾难:访问已释放内存

解决方案对比

方案优点缺点适用场景
原始指针简单直接,无额外开销容易产生悬空指针简单场景,生命周期明确
shared_ptr自动内存管理循环引用导致内存泄漏需要共享所有权的场景
weak_ptr避免循环引用,安全使用稍复杂观察者模式推荐方案

2.3 性能与扩展性权衡

通知性能考量

  • 观察者数量增加时,通知开销线性增长
  • 可能需要支持按优先级通知
  • 考虑异步通知机制避免阻塞

扩展性设计

  • 支持不同类型的观察者
  • 允许观察者过滤感兴趣的事件
  • 提供批量更新机制

3. 实例与应用场景

3.1 案例一:股票价格监控系统

这是一个典型的观察者模式应用,多个投资者监控同一支股票的价格变化。

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <string>
#include <unordered_map>// 前向声明
class StockSubject;/*** @brief 观察者接口* * 定义观察者必须实现的更新接口,用于接收主题状态变化的通知。*/
class StockObserver : public std::enable_shared_from_this<StockObserver> {
public:virtual ~StockObserver() = default;/*** @brief 更新方法* * 当观察的主题状态发生变化时,主题会调用此方法通知观察者。* * @in:*   - subject: 发生状态变化的主题对象指针*/virtual void update(const StockSubject* subject) = 0;/*** @brief 获取观察者名称* * @return:观察者的标识名称*/virtual std::string getName() const = 0;
};/*** @brief 股票主题抽象基类* * 维护观察者列表,提供观察者的注册、注销和通知功能。*/
class StockSubject {
protected:std::string stock_symbol_;    ///< 股票代码double current_price_;        ///< 当前价格/*** @brief 观察者列表(使用weak_ptr避免循环引用)*/std::vector<std::weak_ptr<StockObserver>> observers_;public:/*** @brief 构造函数* * @in:*   - symbol: 股票代码*   - initial_price: 初始价格*/StockSubject(const std::string& symbol, double initial_price): stock_symbol_(symbol), current_price_(initial_price) {}virtual ~StockSubject() = default;/*** @brief 注册观察者* * 将观察者添加到观察者列表中,使其能够接收状态更新通知。* * @in:*   - observer: 要注册的观察者共享指针*/void attach(const std::shared_ptr<StockObserver>& observer) {// 检查是否已经注册auto it = std::find_if(observers_.begin(), observers_.end(),[&observer](const std::weak_ptr<StockObserver>& wp) {return !wp.expired() && wp.lock() == observer;});if (it == observers_.end()) {observers_.emplace_back(observer);std::cout << "观察者 " << observer->getName() << " 开始监控股票 " << stock_symbol_ << std::endl;}}/*** @brief 注销观察者* * 从观察者列表中移除指定的观察者,使其不再接收通知。* * @in:*   - observer: 要注销的观察者共享指针*/void detach(const std::shared_ptr<StockObserver>& observer) {observers_.erase(std::remove_if(observers_.begin(), observers_.end(),[&observer](const std::weak_ptr<StockObserver>& wp) {return wp.expired() || wp.lock() == observer;}),observers_.end());std::cout << "观察者 " << observer->getName() << " 停止监控股票 " << stock_symbol_ << std::endl;}/*** @brief 通知所有观察者* * 遍历观察者列表,调用每个观察者的update方法。* 自动清理已失效的观察者引用。*/void notify() {// 移除已失效的观察者observers_.erase(std::remove_if(observers_.begin(), observers_.end(),[](const std::weak_ptr<StockObserver>& wp) {return wp.expired();}),observers_.end());// 通知所有有效观察者for (auto& weak_observer : observers_) {if (auto observer = weak_observer.lock()) {observer->update(this);}}}/*** @brief 获取股票代码* * @return:股票代码字符串*/std::string getSymbol() const { return stock_symbol_; }/*** @brief 获取当前价格* * @return:当前股票价格*/double getPrice() const { return current_price_; }/*** @brief 获取观察者数量* * @return:当前注册的观察者数量*/size_t getObserverCount() const {return std::count_if(observers_.begin(), observers_.end(),[](const std::weak_ptr<StockObserver>& wp) {return !wp.expired();});}
};/*** @brief 具体股票主题* * 代表一支具体的股票,可以更新价格并通知所有观察者。*/
class ConcreteStock : public StockSubject {
public:/*** @brief 构造函数* * @in:*   - symbol: 股票代码*   - initial_price: 初始价格*/ConcreteStock(const std::string& symbol, double initial_price): StockSubject(symbol, initial_price) {}/*** @brief 更新股票价格* * 设置新的股票价格,如果价格发生变化则通知所有观察者。* * @in:*   - new_price: 新的股票价格*/void setPrice(double new_price) {if (current_price_ != new_price) {double old_price = current_price_;current_price_ = new_price;std::cout << "\n股票 " << stock_symbol_ << " 价格变化: " << old_price << " -> " << new_price << std::endl;// 通知所有观察者notify();}}
};/*** @brief 投资者观察者* * 代表监控股票价格的投资者,在价格变化时执行相应的投资策略。*/
class Investor : public StockObserver {
private:std::string name_;                    ///< 投资者名称double buy_threshold_;               ///< 买入阈值double sell_threshold_;              ///< 卖出阈值std::string last_action_;            ///< 上次操作记录public:/*** @brief 构造函数* * @in:*   - name: 投资者名称*   - buy_threshold: 价格低于此值时考虑买入*   - sell_threshold: 价格高于此值时考虑卖出*/Investor(const std::string& name, double buy_threshold, double sell_threshold): name_(name), buy_threshold_(buy_threshold), sell_threshold_(sell_threshold), last_action_("无操作") {}/*** @brief 更新方法实现* * 接收股票价格变化通知,根据投资策略决定是否买入或卖出。* * @in:*   - subject: 发生价格变化的股票主题*/void update(const StockSubject* subject) override {double current_price = subject->getPrice();const std::string& symbol = subject->getSymbol();std::cout << "[" << name_ << "] 收到通知: " << symbol << " 当前价格 = " << current_price << std::endl;// 投资策略决策if (current_price <= buy_threshold_) {last_action_ = "买入";std::cout << "  → 决策: " << last_action_ << " " << symbol << " (价格 <= " << buy_threshold_ << ")" << std::endl;} else if (current_price >= sell_threshold_) {last_action_ = "卖出";std::cout << "  → 决策: " << last_action_ << " " << symbol << " (价格 >= " << sell_threshold_ << ")" << std::endl;} else {last_action_ = "持有";std::cout << "  → 决策: " << last_action_ << " " << symbol << " (观望中)" << std::endl;}}/*** @brief 获取观察者名称* * @return:投资者名称*/std::string getName() const override {return name_;}/*** @brief 获取上次操作* * @return:上次投资操作描述*/std::string getLastAction() const {return last_action_;}
};/*** @brief 机构观察者* * 代表机构投资者,具有更复杂的监控逻辑和报表功能。*/
class Institution : public StockObserver {
private:std::string institution_name_;       ///< 机构名称std::unordered_map<std::string, double> price_history_;  ///< 价格历史记录public:/*** @brief 构造函数* * @in:*   - name: 机构名称*/explicit Institution(const std::string& name) : institution_name_(name) {}/*** @brief 更新方法实现* * 记录价格历史并生成分析报告。* * @in:*   - subject: 发生价格变化的股票主题*/void update(const StockSubject* subject) override {const std::string& symbol = subject->getSymbol();double current_price = subject->getPrice();// 记录价格历史auto it = price_history_.find(symbol);if (it != price_history_.end()) {double old_price = it->second;double change = ((current_price - old_price) / old_price) * 100;std::cout << "[" << institution_name_ << "] 分析报告 - " << symbol << ": " << old_price << " → " << current_price<< " (" << (change >= 0 ? "+" : "") << change << "%)" << std::endl;}price_history_[symbol] = current_price;}/*** @brief 获取观察者名称* * @return:机构名称*/std::string getName() const override {return institution_name_;}/*** @brief 生成详细报告* * @return:包含所有监控股票的分析报告*/std::string generateReport() const {std::string report = institution_name_ + " 监控报告:\n";for (const auto& entry : price_history_) {report += "  " + entry.first + ": " + std::to_string(entry.second) + "\n";}return report;}
};/*** @brief 主函数 - 股票监控系统演示* * 演示观察者模式在金融监控系统中的应用,展示动态订阅和自动通知机制。* * @return:程序退出码*/
int main() {std::cout << "=== 股票价格监控系统演示 ===" << std::endl;// 创建股票主题auto apple_stock = std::make_shared<ConcreteStock>("AAPL", 150.0);auto google_stock = std::make_shared<ConcreteStock>("GOOGL", 2800.0);// 创建投资者观察者auto investor1 = std::make_shared<Investor>("张投资者", 145.0, 160.0);auto investor2 = std::make_shared<Investor>("李投资者", 140.0, 155.0);auto institution = std::make_shared<Institution>("量子基金");std::cout << "\n--- 初始订阅阶段 ---" << std::endl;// 投资者订阅股票apple_stock->attach(investor1);apple_stock->attach(investor2);apple_stock->attach(institution);google_stock->attach(investor1);google_stock->attach(institution);std::cout << "\nAAPL 观察者数量: " << apple_stock->getObserverCount() << std::endl;std::cout << "GOOGL 观察者数量: " << google_stock->getObserverCount() << std::endl;std::cout << "\n--- 价格变化阶段 ---" << std::endl;// 模拟价格变化apple_stock->setPrice(148.5);  // 小幅下跌apple_stock->setPrice(142.0);  // 大幅下跌apple_stock->setPrice(158.0);  // 大幅上涨google_stock->setPrice(2750.0); // 小幅下跌google_stock->setPrice(2850.0); // 上涨std::cout << "\n--- 动态订阅调整 ---" << std::endl;// 动态取消订阅apple_stock->detach(investor2);std::cout << "AAPL 观察者数量: " << apple_stock->getObserverCount() << std::endl;// 继续价格变化apple_stock->setPrice(162.0);std::cout << "\n--- 机构报告生成 ---" << std::endl;std::cout << institution->generateReport();std::cout << "\n--- 生命周期安全测试 ---" << std::endl;{// 创建临时观察者auto temp_investor = std::make_shared<Investor>("临时投资者", 150.0, 165.0);apple_stock->attach(temp_investor);std::cout << "临时观察者订阅后,AAPL 观察者数量: " << apple_stock->getObserverCount() << std::endl;// temp_investor 离开作用域自动销毁}// 即使临时观察者已销毁,这里也不会崩溃apple_stock->setPrice(155.0);std::cout << "临时观察者销毁后,AAPL 观察者数量: " << apple_stock->getObserverCount() << std::endl;std::cout << "\n=== 演示完成 ===" << std::endl;return 0;
}

Mermaid时序图

AppleStock张投资者李投资者量子基金初始状态:价格150.0attach(张投资者)attach(李投资者)attach(量子基金)setPrice(148.5)update()update()update()所有观察者收到价格下跌通知setPrice(142.0)update()update()update()detach(李投资者)setPrice(158.0)update()update()李投资者已取消订阅,不再接收通知AppleStock张投资者李投资者量子基金

3.2 案例二:游戏引擎事件系统

游戏引擎中广泛使用观察者模式来处理各种游戏事件。

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>// 游戏事件类型枚举
enum class EventType {PLAYER_SPAWN,PLAYER_DEATH,ITEM_PICKUP,LEVEL_COMPLETE,ACHIEVEMENT_UNLOCKED
};/*** @brief 游戏事件数据基类* * 所有具体游戏事件数据的基类,提供基本的事件信息。*/
struct GameEvent {EventType type;             ///< 事件类型double timestamp;          ///< 事件时间戳virtual ~GameEvent() = default;/*** @brief 获取事件描述* * @return:事件的文字描述*/virtual std::string getDescription() const = 0;
};/*** @brief 玩家生成事件*/
struct PlayerSpawnEvent : public GameEvent {std::string player_name;    ///< 玩家名称int spawn_point_id;         ///< 生成点IDPlayerSpawnEvent(const std::string& name, int spawn_id) : player_name(name), spawn_point_id(spawn_id) {type = EventType::PLAYER_SPAWN;}std::string getDescription() const override {return "玩家 " + player_name + " 在生成点 " + std::to_string(spawn_point_id) + " 生成";}
};/*** @brief 玩家死亡事件*/
struct PlayerDeathEvent : public GameEvent {std::string player_name;    ///< 玩家名称std::string killer_name;    ///< 击杀者名称std::string cause;          ///< 死亡原因PlayerDeathEvent(const std::string& player, const std::string& killer, const std::string& death_cause): player_name(player), killer_name(killer), cause(death_cause) {type = EventType::PLAYER_DEATH;}std::string getDescription() const override {return "玩家 " + player_name + " 被 " + killer_name + " 击杀 (" + cause + ")";}
};/*** @brief 成就解锁事件*/
struct AchievementEvent : public GameEvent {std::string achievement_id;  ///< 成就IDstd::string achievement_name; ///< 成就名称int points;                 ///< 成就点数AchievementEvent(const std::string& id, const std::string& name, int pts): achievement_id(id), achievement_name(name), points(pts) {type = EventType::ACHIEVEMENT_UNLOCKED;}std::string getDescription() const override {return "解锁成就: " + achievement_name + " (" + std::to_string(points) + "点)";}
};// 前向声明
class EventManager;/*** @brief 游戏事件观察者接口* * 游戏系统中各种组件通过实现此接口来监听感兴趣的游戏事件。*/
class GameEventListener {
public:virtual ~GameEventListener() = default;/*** @brief 事件处理接口* * @in:*   - event: 发生的事件对象*   - manager: 事件管理器,用于触发新事件*/virtual void onEvent(const std::shared_ptr<GameEvent>& event, EventManager* manager) = 0;/*** @brief 获取监听器名称* * @return:监听器标识名称*/virtual std::string getName() const = 0;/*** @brief 获取感兴趣的事件类型* * @return:该监听器关心的事件类型列表*/virtual std::vector<EventType> getInterestedEvents() const = 0;
};/*** @brief 事件管理器* * 游戏事件系统的核心,负责事件的发布和观察者管理。*/
class EventManager {
private:/*** @brief 事件类型到观察者列表的映射*/std::unordered_map<EventType, std::vector<std::weak_ptr<GameEventListener>>> event_listeners_;public:/*** @brief 注册事件监听器* * 将监听器注册到其感兴趣的所有事件类型上。* * @in:*   - listener: 要注册的事件监听器*/void registerListener(const std::shared_ptr<GameEventListener>& listener) {for (EventType event_type : listener->getInterestedEvents()) {event_listeners_[event_type].emplace_back(listener);std::cout << "注册监听器: " << listener->getName() << " -> 事件类型: " << static_cast<int>(event_type) << std::endl;}}/*** @brief 取消注册事件监听器* * 从所有事件类型中移除指定的监听器。* * @in:*   - listener: 要取消注册的事件监听器*/void unregisterListener(const std::shared_ptr<GameEventListener>& listener) {for (auto& pair : event_listeners_) {auto& listeners = pair.second;listeners.erase(std::remove_if(listeners.begin(), listeners.end(),[&listener](const std::weak_ptr<GameEventListener>& wp) {return wp.expired() || wp.lock() == listener;}),listeners.end());}std::cout << "取消注册监听器: " << listener->getName() << std::endl;}/*** @brief 发布事件* * 将事件发送给所有注册的对应类型监听器。* * @in:*   - event: 要发布的事件对象*/void publishEvent(const std::shared_ptr<GameEvent>& event) {std::cout << "\n[事件发布] " << event->getDescription() << std::endl;auto it = event_listeners_.find(event->type);if (it != event_listeners_.end()) {// 清理失效的监听器auto& listeners = it->second;listeners.erase(std::remove_if(listeners.begin(), listeners.end(),[](const std::weak_ptr<GameEventListener>& wp) {return wp.expired();}),listeners.end());// 通知所有有效监听器for (auto& weak_listener : listeners) {if (auto listener = weak_listener.lock()) {listener->onEvent(event, this);}}}}/*** @brief 获取监听器统计信息* * @return:各事件类型的监听器数量统计*/std::string getStats() const {std::string stats = "事件监听器统计:\n";for (const auto& pair : event_listeners_) {int count = std::count_if(pair.second.begin(), pair.second.end(),[](const std::weak_ptr<GameEventListener>& wp) {return !wp.expired();});stats += "  事件类型 " + std::to_string(static_cast<int>(pair.first)) + ": " + std::to_string(count) + " 个监听器\n";}return stats;}
};/*** @brief 成就系统* * 监听游戏事件并解锁相应的成就。*/
class AchievementSystem : public GameEventListener {
private:std::unordered_map<std::string, bool> unlocked_achievements_;  ///< 已解锁成就记录int total_points_;                                             ///< 总成就点数public:AchievementSystem() : total_points_(0) {// 初始化成就列表unlocked_achievements_ = {{"first_blood", false},{"veteran", false},{"collector", false}};}void onEvent(const std::shared_ptr<GameEvent>& event, EventManager* manager) override {switch (event->type) {case EventType::PLAYER_DEATH: {// 检查是否是第一次击杀if (!unlocked_achievements_["first_blood"]) {unlocked_achievements_["first_blood"] = true;total_points_ += 10;// 发布成就解锁事件auto achievement_event = std::make_shared<AchievementEvent>("first_blood", "第一滴血", 10);manager->publishEvent(achievement_event);}break;}case EventType::PLAYER_SPAWN: {auto spawn_event = std::dynamic_pointer_cast<PlayerSpawnEvent>(event);if (spawn_event && spawn_event->spawn_point_id > 5 && !unlocked_achievements_["veteran"]) {unlocked_achievements_["veteran"] = true;total_points_ += 20;auto achievement_event = std::make_shared<AchievementEvent>("veteran", "战场老兵", 20);manager->publishEvent(achievement_event);}break;}case EventType::ITEM_PICKUP: {// 简单的物品收集成就逻辑if (!unlocked_achievements_["collector"]) {unlocked_achievements_["collector"] = true;total_points_ += 15;auto achievement_event = std::make_shared<AchievementEvent>("collector", "收藏家", 15);manager->publishEvent(achievement_event);}break;}default:break;}}std::string getName() const override {return "成就系统";}std::vector<EventType> getInterestedEvents() const override {return {EventType::PLAYER_DEATH,EventType::PLAYER_SPAWN,EventType::ITEM_PICKUP};}/*** @brief 获取成就系统状态* * @return:成就解锁状态和点数统计*/std::string getStatus() const {std::string status = "成就系统状态:\n";status += "  总点数: " + std::to_string(total_points_) + "\n";status += "  已解锁成就:\n";for (const auto& achievement : unlocked_achievements_) {status += "    " + achievement.first + ": " + (achievement.second ? "已解锁" : "未解锁") + "\n";}return status;}
};/*** @brief 统计系统* * 收集游戏统计数据,如击杀数、死亡数等。*/
class StatisticsSystem : public GameEventListener {
private:int total_kills_;           ///< 总击杀数int total_deaths_;          ///< 总死亡数int total_spawns_;          ///< 总生成次数public:StatisticsSystem() : total_kills_(0), total_deaths_(0), total_spawns_(0) {}void onEvent(const std::shared_ptr<GameEvent>& event, EventManager* manager) override {switch (event->type) {case EventType::PLAYER_DEATH:total_deaths_++;break;case EventType::PLAYER_SPAWN:total_spawns_++;break;default:break;}}std::string getName() const override {return "统计系统";}std::vector<EventType> getInterestedEvents() const override {return {EventType::PLAYER_DEATH,EventType::PLAYER_SPAWN};}/*** @brief 获取统计报告* * @return:游戏统计数据报告*/std::string getReport() const {return "游戏统计:\n  生成次数: " + std::to_string(total_spawns_) +"\n  死亡次数: " + std::to_string(total_deaths_) +"\n  K/D比率: " + (total_deaths_ > 0 ? std::to_string(static_cast<double>(total_kills_) / total_deaths_) : "N/A");}
};/*** @brief 用户界面系统* * 在游戏UI中显示事件通知。*/
class UISystem : public GameEventListener {
public:void onEvent(const std::shared_ptr<GameEvent>& event, EventManager* manager) override {std::cout << "[UI通知] " << event->getDescription() << std::endl;}std::string getName() const override {return "UI系统";}std::vector<EventType> getInterestedEvents() const override {// UI系统关心所有事件类型return {EventType::PLAYER_SPAWN,EventType::PLAYER_DEATH,EventType::ITEM_PICKUP,EventType::LEVEL_COMPLETE,EventType::ACHIEVEMENT_UNLOCKED};}
};/*** @brief 主函数 - 游戏事件系统演示* * 演示观察者模式在游戏事件系统中的应用,展示多类型事件的处理机制。* * @return:程序退出码*/
int main() {std::cout << "=== 游戏引擎事件系统演示 ===" << std::endl;// 创建事件管理器EventManager event_manager;// 创建各种游戏系统auto achievement_system = std::make_shared<AchievementSystem>();auto stats_system = std::make_shared<StatisticsSystem>();auto ui_system = std::make_shared<UISystem>();// 注册系统到事件管理器std::cout << "\n--- 系统注册阶段 ---" << std::endl;event_manager.registerListener(achievement_system);event_manager.registerListener(stats_system);event_manager.registerListener(ui_system);std::cout << event_manager.getStats();std::cout << "\n--- 游戏事件模拟 ---" << std::endl;// 模拟游戏事件序列auto spawn_event1 = std::make_shared<PlayerSpawnEvent>("玩家1", 1);event_manager.publishEvent(spawn_event1);auto spawn_event2 = std::make_shared<PlayerSpawnEvent>("玩家2", 6); // 高级生成点event_manager.publishEvent(spawn_event2);auto death_event1 = std::make_shared<PlayerDeathEvent>("玩家1", "玩家2", "爆头");event_manager.publishEvent(death_event1);// 模拟物品拾取事件auto pickup_event = std::make_shared<GameEvent>();pickup_event->type = EventType::ITEM_PICKUP;event_manager.publishEvent(pickup_event);std::cout << "\n--- 系统状态报告 ---" << std::endl;std::cout << achievement_system->getStatus() << std::endl;std::cout << stats_system->getReport() << std::endl;std::cout << "\n--- 动态系统管理 ---" << std::endl;// 动态移除UI系统event_manager.unregisterListener(ui_system);// 后续事件不再通知UI系统auto death_event2 = std::make_shared<PlayerDeathEvent>("玩家2", "玩家1", "近战");event_manager.publishEvent(death_event2);std::cout << event_manager.getStats();std::cout << "\n=== 演示完成 ===" << std::endl;return 0;
}

Mermaid流程图

游戏事件发生
EventManager.publishEvent
查找对应事件类型的监听器列表
清理已失效的监听器
遍历有效监听器
监听器处理事件?
调用onEvent方法
跳过此监听器
监听器可能发布新事件
所有监听器处理完成?
事件处理完成

3.3 案例三:配置管理系统

企业级配置管理系统使用观察者模式实现配置热更新。

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <unordered_map>
#include <functional>
#include <sstream>/*** @brief 配置变更观察者接口* * 组件通过实现此接口来监听配置变化并自动更新自身状态。*/
class ConfigChangeListener {
public:virtual ~ConfigChangeListener() = default;/*** @brief 配置变更回调* * 当监听的配置项发生变化时调用此方法。* * @in:*   - key: 发生变化的配置键*   - old_value: 旧的配置值*   - new_value: 新的配置值*/virtual void onConfigChanged(const std::string& key, const std::string& old_value, const std::string& new_value) = 0;/*** @brief 获取监听器名称* * @return:监听器标识名称*/virtual std::string getName() const = 0;/*** @brief 获取关心的配置键* * @return:该监听器关心的配置键列表*/virtual std::vector<std::string> getWatchedKeys() const = 0;
};/*** @brief 配置管理器* * 管理应用程序的配置数据,支持配置热更新和观察者通知。*/
class ConfigManager {
private:std::unordered_map<std::string, std::string> config_data_;  ///< 配置数据存储/*** @brief 配置键到观察者列表的映射*/std::unordered_map<std::string, std::vector<std::weak_ptr<ConfigChangeListener>>> key_listeners_;/*** @brief 全局观察者列表(监听所有配置变化)*/std::vector<std::weak_ptr<ConfigChangeListener>> global_listeners_;public:/*** @brief 设置配置值* * 设置指定配置键的值,如果值发生变化则通知所有观察者。* * @in:*   - key: 配置键*   - value: 配置值*/void setConfig(const std::string& key, const std::string& value) {std::string old_value = getConfig(key, "");if (old_value != value) {config_data_[key] = value;std::cout << "配置更新: " << key << " = " << value << " (原值: " << (old_value.empty() ? "空" : old_value) << ")" << std::endl;// 通知特定键的观察者notifyKeyListeners(key, old_value, value);// 通知全局观察者notifyGlobalListeners(key, old_value, value);}}/*** @brief 获取配置值* * @in:*   - key: 配置键*   - default_value: 如果键不存在时返回的默认值* * @return:配置值或默认值*/std::string getConfig(const std::string& key, const std::string& default_value = "") const {auto it = config_data_.find(key);return it != config_data_.end() ? it->second : default_value;}/*** @brief 注册键监听器* * 注册观察者监听特定配置键的变化。* * @in:*   - key: 要监听的配置键*   - listener: 配置变更观察者*/void registerKeyListener(const std::string& key, const std::shared_ptr<ConfigChangeListener>& listener) {key_listeners_[key].emplace_back(listener);std::cout << "注册键监听: " << listener->getName() << " -> " << key << std::endl;}/*** @brief 注册全局监听器* * 注册观察者监听所有配置键的变化。* * @in:*   - listener: 配置变更观察者*/void registerGlobalListener(const std::shared_ptr<ConfigChangeListener>& listener) {global_listeners_.emplace_back(listener);std::cout << "注册全局监听: " << listener->getName() << std::endl;}/*** @brief 批量注册监听器* * 根据观察者关心的键列表自动注册监听。* * @in:*   - listener: 配置变更观察者*/void registerListener(const std::shared_ptr<ConfigChangeListener>& listener) {for (const auto& key : listener->getWatchedKeys()) {registerKeyListener(key, listener);}}/*** @brief 获取配置快照* * @return:当前所有配置的字符串表示*/std::string getConfigSnapshot() const {std::stringstream ss;ss << "当前配置快照:\n";for (const auto& entry : config_data_) {ss << "  " << entry.first << " = " << entry.second << "\n";}return ss.str();}private:/*** @brief 通知键监听器* * 通知监听特定配置键的观察者。* * @in:*   - key: 发生变化的配置键*   - old_value: 旧值*   - new_value: 新值*/void notifyKeyListeners(const std::string& key, const std::string& old_value, const std::string& new_value) {auto it = key_listeners_.find(key);if (it != key_listeners_.end()) {auto& listeners = it->second;// 清理失效的监听器listeners.erase(std::remove_if(listeners.begin(), listeners.end(),[](const std::weak_ptr<ConfigChangeListener>& wp) {return wp.expired();}),listeners.end());// 通知有效监听器for (auto& weak_listener : listeners) {if (auto listener = weak_listener.lock()) {listener->onConfigChanged(key, old_value, new_value);}}}}/*** @brief 通知全局监听器* * 通知监听所有配置变化的观察者。* * @in:*   - key: 发生变化的配置键*   - old_value: 旧值*   - new_value: 新值*/void notifyGlobalListeners(const std::string& key, const std::string& old_value, const std::string& new_value) {// 清理失效的监听器global_listeners_.erase(std::remove_if(global_listeners_.begin(), global_listeners_.end(),[](const std::weak_ptr<ConfigChangeListener>& wp) {return wp.expired();}),global_listeners_.end());// 通知有效监听器for (auto& weak_listener : global_listeners_) {if (auto listener = weak_listener.lock()) {listener->onConfigChanged(key, old_value, new_value);}}}
};/*** @brief 数据库连接池* * 根据数据库配置变化动态调整连接池参数。*/
class DatabaseConnectionPool : public ConfigChangeListener {
private:std::string pool_name_;                 ///< 连接池名称int max_connections_;                   ///< 最大连接数int timeout_seconds_;                   ///< 超时时间public:explicit DatabaseConnectionPool(const std::string& name) : pool_name_(name), max_connections_(10), timeout_seconds_(30) {}void onConfigChanged(const std::string& key, const std::string& old_value, const std::string& new_value) override {if (key == "db.max_connections") {int new_max = std::stoi(new_value);if (new_max != max_connections_) {std::cout << "[" << pool_name_ << "] 更新最大连接数: " << max_connections_ << " -> " << new_max << std::endl;max_connections_ = new_max;applyPoolConfig();}} else if (key == "db.timeout") {int new_timeout = std::stoi(new_value);if (new_timeout != timeout_seconds_) {std::cout << "[" << pool_name_ << "] 更新超时时间: " << timeout_seconds_ << " -> " << new_timeout << "秒" << std::endl;timeout_seconds_ = new_timeout;applyPoolConfig();}}}std::string getName() const override {return "数据库连接池[" + pool_name_ + "]";}std::vector<std::string> getWatchedKeys() const override {return {"db.max_connections","db.timeout"};}/*** @brief 应用连接池配置* * 根据当前配置参数调整连接池状态。*/void applyPoolConfig() {std::cout << "[" << pool_name_ << "] 应用配置: " << max_connections_ << "连接, " << timeout_seconds_ << "秒超时" << std::endl;}/*** @brief 获取连接池状态* * @return:连接池当前状态描述*/std::string getStatus() const {return "连接池状态: " + std::to_string(max_connections_) + "连接, " + std::to_string(timeout_seconds_) + "秒超时";}
};/*** @brief 日志系统* * 根据日志配置变化动态调整日志级别和输出目标。*/
class LogSystem : public ConfigChangeListener {
private:std::string log_level_;                 ///< 当前日志级别std::string output_target_;             ///< 输出目标public:LogSystem() : log_level_("INFO"), output_target_("console") {}void onConfigChanged(const std::string& key, const std::string& old_value, const std::string& new_value) override {if (key == "log.level") {std::cout << "[日志系统] 更新日志级别: " << log_level_ << " -> " << new_value << std::endl;log_level_ = new_value;applyLogConfig();} else if (key == "log.output") {std::cout << "[日志系统] 更新输出目标: " << output_target_ << " -> " << new_value << std::endl;output_target_ = new_value;applyLogConfig();}}std::string getName() const override {return "日志系统";}std::vector<std::string> getWatchedKeys() const override {return {"log.level","log.output"};}/*** @brief 应用日志配置* * 根据当前配置参数调整日志系统行为。*/void applyLogConfig() {std::cout << "[日志系统] 应用配置: 级别=" << log_level_ << ", 输出=" << output_target_ << std::endl;}/*** @brief 记录日志* * @in:*   - level: 日志级别*   - message: 日志消息*/void log(const std::string& level, const std::string& message) {// 简单的级别过滤if (shouldLog(level)) {std::cout << "[" << level << "] " << message << std::endl;}}private:/*** @brief 检查是否应该记录指定级别的日志* * @in:*   - level: 要检查的日志级别* * @return:如果应该记录返回true*/bool shouldLog(const std::string& level) const {static const std::unordered_map<std::string, int> level_priority = {{"DEBUG", 0},{"INFO", 1},{"WARN", 2},{"ERROR", 3},{"FATAL", 4}};auto current_it = level_priority.find(log_level_);auto check_it = level_priority.find(level);if (current_it != level_priority.end() && check_it != level_priority.end()) {return check_it->second >= current_it->second;}return false;}
};/*** @brief 配置审计系统* * 记录所有配置变更的审计日志,用于安全监控。*/
class ConfigAuditSystem : public ConfigChangeListener {
public:void onConfigChanged(const std::string& key, const std::string& old_value, const std::string& new_value) override {std::cout << "[审计系统] 配置变更审计: " << key << " = " << old_value << " -> " << new_value << std::endl;// 在实际系统中,这里会将审计记录写入数据库或文件writeAuditLog(key, old_value, new_value);}std::string getName() const override {return "配置审计系统";}std::vector<std::string> getWatchedKeys() const override {// 审计系统关心所有配置键return {}; // 空列表表示监听所有键(通过全局监听注册)}private:/*** @brief 写入审计日志* * @in:*   - key: 配置键*   - old_value: 旧值*   - new_value: 新值*/void writeAuditLog(const std::string& key, const std::string& old_value, const std::string& new_value) {// 模拟写入审计日志// 实际实现可能写入数据库、文件或发送到审计服务static int audit_id = 0;audit_id++;// 这里只是演示,实际系统会有更完整的审计记录}
};/*** @brief 主函数 - 配置管理系统演示* * 演示观察者模式在配置管理系统中的应用,展示配置热更新和组件自动调整。* * @return:程序退出码*/
int main() {std::cout << "=== 配置管理系统演示 ===" << std::endl;// 创建配置管理器ConfigManager config_manager;// 创建各种系统组件auto db_pool = std::make_shared<DatabaseConnectionPool>("主数据库");auto log_system = std::make_shared<LogSystem>();auto audit_system = std::make_shared<ConfigAuditSystem>();std::cout << "\n--- 组件注册阶段 ---" << std::endl;// 注册组件到配置管理器config_manager.registerListener(db_pool);config_manager.registerListener(log_system);config_manager.registerGlobalListener(audit_system); // 审计系统监听所有变更std::cout << "\n--- 初始配置设置 ---" << std::endl;// 设置初始配置config_manager.setConfig("db.max_connections", "20");config_manager.setConfig("db.timeout", "60");config_manager.setConfig("log.level", "INFO");config_manager.setConfig("log.output", "file");std::cout << config_manager.getConfigSnapshot();std::cout << "\n--- 配置热更新演示 ---" << std::endl;// 模拟运行时配置更新config_manager.setConfig("db.max_connections", "50");  // 增加连接数config_manager.setConfig("log.level", "DEBUG");        // 调整日志级别config_manager.setConfig("app.cache_size", "1024");    // 新增配置(审计系统会记录)std::cout << "\n--- 组件状态检查 ---" << std::endl;// 检查组件状态log_system->log("DEBUG", "这是一条调试消息");log_system->log("INFO", "这是一条信息消息");std::cout << "\n--- 批量配置更新 ---" << std::endl;// 模拟从外部配置文件重新加载配置config_manager.setConfig("db.max_connections", "30");config_manager.setConfig("db.timeout", "45");config_manager.setConfig("log.level", "WARN");config_manager.setConfig("log.output", "console");std::cout << "\n最终配置状态:" << std::endl;std::cout << config_manager.getConfigSnapshot();std::cout << "\n=== 演示完成 ===" << std::endl;return 0;
}

4. Makefile范例

# 编译器配置
CXX := g++
CXX_STD := c++17# 编译选项
CXXFLAGS := -std=$(CXX_STD) -Wall -Wextra -Wpedantic -O2
DEBUG_FLAGS := -g -DDEBUG -O0
RELEASE_FLAGS := -DNDEBUG -O3# 目标文件
TARGETS := stock_system game_events config_system# 默认构建所有目标
all: $(TARGETS)# 股票监控系统
stock_system: stock_system.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 游戏事件系统
game_events: game_events.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 配置管理系统
config_system: config_system.cpp$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ $<# 调试版本
debug: CXXFLAGS += $(DEBUG_FLAGS)
debug: $(TARGETS)# 清理构建文件
clean:rm -f $(TARGETS) *.o# 安装依赖(Ubuntu/Debian)
install-deps:sudo apt-get updatesudo apt-get install -y g++ build-essential# 运行所有测试
test: all@echo "运行股票系统测试..."@./stock_system@echo@echo "运行游戏事件系统测试..."@./game_events@echo@echo "运行配置系统测试..."@./config_system.PHONY: all clean debug install-deps test

5. 操作说明

5.1 编译方法

环境要求

  • GCC 7.0+ 或 Clang 5.0+
  • C++17 支持
  • Linux/Unix 系统(Windows需使用MinGW或WSL)

编译命令

# 安装编译依赖(Ubuntu/Debian)
make install-deps# 编译所有目标
make# 编译调试版本
make debug# 编译并运行所有测试
make test

5.2 运行方式

股票监控系统

./stock_system

输出示例:

=== 股票价格监控系统演示 ===--- 初始订阅阶段 ---
观察者 张投资者 开始监控股票 AAPL
观察者 李投资者 开始监控股票 AAPL
观察者 量子基金 开始监控股票 AAPL
观察者 张投资者 开始监控股票 GOOGL
观察者 量子基金 开始监控股票 GOOGL--- 价格变化阶段 ---
股票 AAPL 价格变化: 150 -> 148.5
[张投资者] 收到通知: AAPL 当前价格 = 148.5→ 决策: 持有 AAPL (观望中)
[李投资者] 收到通知: AAPL 当前价格 = 148.5→ 决策: 持有 AAPL (观望中)
[量子基金] 分析报告 - AAPL: 150 → 148.5 (-1%)

游戏事件系统

./game_events

输出示例:

=== 游戏引擎事件系统演示 ===--- 系统注册阶段 ---
注册监听器: 成就系统 -> 事件类型: 2
注册监听器: 成就系统 -> 事件类型: 0
注册监听器: 成就系统 -> 事件类型: 1
注册监听器: 统计系统 -> 事件类型: 2
注册监听器: 统计系统 -> 事件类型: 0
注册监听器: UI系统 -> 事件类型: 0
...--- 游戏事件模拟 ---
[事件发布] 玩家 玩家1 在生成点 1 生成
[UI通知] 玩家 玩家1 在生成点 1 生成
[事件发布] 玩家 玩家2 在生成点 6 生成
[UI通知] 玩家 玩家2 在生成点 6 生成
[成就系统] 分析报告 - : 0 → 0 (+0%)

配置管理系统

./config_system

输出示例:

=== 配置管理系统演示 ===--- 组件注册阶段 ---
注册键监听: 数据库连接池[主数据库] -> db.max_connections
注册键监听: 数据库连接池[主数据库] -> db.timeout
注册键监听: 日志系统 -> log.level
注册键监听: 日志系统 -> log.output
注册全局监听: 配置审计系统--- 初始配置设置 ---
配置更新: db.max_connections = 20 (原值: 空)
[审计系统] 配置变更审计: db.max_connections =  -> 20
配置更新: db.timeout = 60 (原值: 空)
[审计系统] 配置变更审计: db.timeout =  -> 60
...

5.3 结果解读

正常输出特征

  • 股票系统:投资者根据价格阈值自动做出买卖决策
  • 游戏系统:各子系统协同处理游戏事件,成就自动解锁
  • 配置系统:配置变更时各组件自动调整行为

设计模式优势体现

  • 解耦性:主题和观察者相互独立,可以独立变化
  • 扩展性:新增观察者无需修改主题代码
  • 动态性:运行时可以动态添加、删除观察者关系

6. 深入解析:生命周期管理的最佳实践

6.1 weak_ptr的安全使用模式

观察者模式中最重要的技术点就是使用std::weak_ptr避免循环引用和悬空指针:

class SafeSubject {
private:std::vector<std::weak_ptr<Observer>> observers_;public:void attach(const std::shared_ptr<Observer>& observer) {// 使用weak_ptr避免循环引用observers_.push_back(observer);}void notify() {// 先清理已失效的观察者observers_.erase(std::remove_if(observers_.begin(), observers_.end(),[](const auto& wp) { return wp.expired(); }),observers_.end());// 再通知有效的观察者for (auto& weak_obs : observers_) {if (auto observer = weak_obs.lock()) {observer->update();}}}
};

6.2 观察者模式的变体与扩展

1. 推模型 vs 拉模型

// 推模型:主题将详细数据推送给观察者
virtual void update(const EventData& data) = 0;// 拉模型:观察者从主题拉取需要的数据  
virtual void update(Subject* subject) = 0;

2. 事件过滤机制

class SelectiveObserver : public Observer {
private:std::function<bool(const EventData&)> filter_;public:void update(const EventData& data) override {if (filter_(data)) {// 只处理符合条件的事件processEvent(data);}}
};

3. 异步通知机制

class AsyncSubject : public Subject {
private:std::thread notification_thread_;public:void asyncNotify() {notification_thread_ = std::thread([this]() {std::this_thread::sleep_for(std::chrono::milliseconds(10));notify();  // 在后台线程通知});}
};

7. 总结

观察者模式是构建松耦合、可扩展系统的利器。通过本文的三个完整案例,我们深入掌握了:

核心价值

  • 实现主题与观察者的解耦,提高代码的可维护性
  • 支持广播通信,简化一对多的依赖关系管理
  • 提供动态订阅机制,增强系统灵活性

关键技术

  • 使用std::weak_ptr安全管理观察者生命周期
  • 通过模板和继承支持多种事件类型
  • 实现高效的通知和清理机制

适用场景

  • 事件驱动架构:GUI系统、游戏引擎
  • 数据监控系统:股票行情、系统监控
  • 配置管理系统:热更新、动态调整
  • 分布式系统:消息队列、发布订阅

观察者模式虽然简单,但却是构建现代软件系统的基础模式。掌握它,就掌握了构建响应式、可扩展系统的关键技能。

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

相关文章:

  • 网站建设报表明细新手做网站看什么书
  • 微课网站开发如何查看网站域名
  • Spring工程 生成表和mapper文件
  • 服装培训网站建设网站图片切换
  • Python爬虫实战:获取丁香人才网招聘信息与数据分析
  • 光学转镜最小长度计算模型:基于视场角与有效口径的匹配算法
  • 汉子由来 外国人做的网站网页设计的尺寸是指
  • 智能驱动与合规双赢:2025年企业DevOps平台选型深度解析
  • 2025年,如何选择Python Web框架:Django, Flask还是FastAPI?
  • FLASK与JAVA的多文件互传(多文件互传亲测)
  • 蓝牙音箱的技术演进:从便捷到高保真的音频革命
  • 打破信息孤岛,构建统一视界:视频融合平台EasyCVR在智慧校园建设中的核心作用
  • 计算机应用技术网站开发基础知识网店推广平台
  • 快速完美解决在CefSharp.WinForms.ChromiumWebBrowser浏览器中无法播放视频的问题
  • 【高并发服务器:前置知识】一、项目介绍 模块划分
  • 数据结构入门 (五):约束即是力量 —— 深入理解栈
  • 如何搭建网站的结构丰台专业网站建设公司
  • Web渗透之一句话木马
  • 网站建设如何选择服务器百度做公司网站多少钱
  • Vscode+CMake编译时出现中文乱码
  • 38、spark读取hudi报错:java.io.NotSerializableException: org.apache.hadoop.fs.Path
  • 三年级上册语文快乐读书吧读书笔记+知识点(格林童话、安徒生童话、稻草人)+三年级语文快乐读书吧笔记汇总+完整电子版可下载打印
  • 迅为Hi3516CV610开发板强劲内核-海思Hi3516CV610核心板
  • 网站开发可以当程序员wordpress 怎么迁移
  • babelfish for postgresql 分析--babelfishpg_tds--doing
  • 手机网站排行榜焦作专业网站建设费用
  • 小程序开发:开启定制化custom-tab-bar但不生效问题,以及使用NutUI-React Taro的安装和使用
  • 避坑指南:关于文件夹加密软件(以“文件夹加密超级大师”为例)卸载前的正确操作流程
  • 用矩阵实现元素绕不定点旋转
  • Web UI自动化测试学习系列5--基础知识1--常用元素定位1