首页/新闻资讯/正文详情

C++观察者模式:原理、实现与工程实践

发布时间:2026/9/22 22:06:56 来源:云帆数科 栏目:资讯中心
C++观察者模式:原理、实现与工程实践
1. 观察者模式的核心价值与应用场景在软件工程领域观察者模式Observer Pattern堪称最经典的行为型设计模式之一。我曾在多个C项目中运用这种模式解决模块间通信问题特别是在需要实现松耦合架构的场景下它的表现尤为出色。想象一下气象站系统的案例当温度传感器检测到数据变化时需要同时更新显示屏、日志记录器和报警系统——这正是观察者模式的用武之地。该模式的核心在于建立一种一对多的依赖关系当被观察对象Subject状态改变时所有依赖它的观察者Observers都会自动收到通知并更新。这种机制完美遵循了面向对象设计原则中的开闭原则对扩展开放对修改关闭使得系统能够在不修改现有代码的情况下增加新的观察者。2. C实现观察者模式的经典结构2.1 基础接口设计我们先定义模式中的两个核心接口。在头文件observer.h中class Observer { public: virtual ~Observer() default; virtual void update(float temperature, float humidity, float pressure) 0; }; class Subject { public: virtual ~Subject() default; virtual void registerObserver(Observer* o) 0; virtual void removeObserver(Observer* o) 0; virtual void notifyObservers() 0; };这里有几个关键设计点使用纯虚函数构成抽象接口确保子类必须实现这些方法添加虚析构函数以避免潜在的内存泄漏update方法参数使用具体气象数据作为示例实际项目中可根据需求调整2.2 具体主题实现以WeatherData为例实现Subject接口class WeatherData : public Subject { private: std::vectorObserver* observers; float temperature; float humidity; float pressure; public: void registerObserver(Observer* o) override { observers.push_back(o); } void removeObserver(Observer* o) override { observers.erase(std::remove(observers.begin(), observers.end(), o), observers.end()); } void notifyObservers() override { for (auto* observer : observers) { observer-update(temperature, humidity, pressure); } } void measurementsChanged() { notifyObservers(); } void setMeasurements(float temp, float humidity, float pressure) { this-temperature temp; this-humidity humidity; this-pressure pressure; measurementsChanged(); } };关键提示在实际项目中observer列表的修改添加/删除需要考虑线程安全问题。简单的做法是用std::mutex保护observers容器。2.3 具体观察者实现下面是两个典型的观察者实现// 显示屏观察者 class CurrentConditionsDisplay : public Observer { private: float temperature; float humidity; Subject* weatherData; public: explicit CurrentConditionsDisplay(Subject* weatherData) : weatherData(weatherData) { weatherData-registerObserver(this); } ~CurrentConditionsDisplay() override { weatherData-removeObserver(this); } void update(float temp, float humidity, float pressure) override { this-temperature temp; this-humidity humidity; display(); } void display() const { std::cout Current conditions: temperature °C and humidity % humidity\n; } }; // 日志记录观察者 class Logger : public Observer { public: void update(float temp, float humidity, float pressure) override { std::ofstream logfile(weather.log, std::ios::app); logfile Weather update - Temp: temp , Humidity: humidity , Pressure: pressure \n; } };3. 现代C的进阶实现技巧3.1 使用智能指针管理生命周期原始指针管理容易导致内存泄漏我们可以用shared_ptr和weak_ptr改进class Subject { public: virtual void registerObserver(std::shared_ptrObserver o) 0; virtual void removeObserver(std::shared_ptrObserver o) 0; // ... }; class WeatherData : public Subject { private: std::vectorstd::weak_ptrObserver observers; // ... };这种改进带来三个优势自动内存管理避免裸指针导致的内存泄漏weak_ptr避免循环引用更安全的对象生命周期管理3.2 使用std::function实现通用观察者C11后我们可以摆脱继承束缚使用函数对象class Subject { public: using ObserverFunc std::functionvoid(float, float, float); void registerObserver(ObserverFunc func) { observers.push_back(func); } void notifyObservers() { for (auto func : observers) { func(temperature, humidity, pressure); } } private: std::vectorObserverFunc observers; // ... }; // 使用lambda注册观察者 weatherData.registerObserver([](float t, float h, float p) { std::cout Lambda observer: t std::endl; });3.3 线程安全实现方案生产环境中需要考虑多线程安全class ThreadSafeWeatherData : public Subject { public: void registerObserver(std::shared_ptrObserver o) override { std::lock_guardstd::mutex lock(mutex); observers.push_back(o); } void notifyObservers() override { std::vectorstd::shared_ptrObserver observersCopy; { std::lock_guardstd::mutex lock(mutex); observersCopy observers; } for (auto observer : observersCopy) { if (auto ptr observer.lock()) { ptr-update(temperature, humidity, pressure); } } } private: std::mutex mutex; std::vectorstd::weak_ptrObserver observers; };4. 实际项目中的经验与陷阱4.1 性能优化策略在观察者数量庞大的系统中通知操作可能成为性能瓶颈。我们可采用以下优化批量更新积累多个变化后一次性通知void setMeasurements(float temp, float humidity, float pressure) { this-temperature temp; this-humidity humidity; this-pressure pressure; if (changeCount batchSize) { measurementsChanged(); changeCount 0; } }差分通知仅当数据变化超过阈值时通知void setMeasurements(float temp, float humidity, float pressure) { if (fabs(temperature - temp) threshold || fabs(humidity - humidity) threshold) { this-temperature temp; this-humidity humidity; this-pressure pressure; measurementsChanged(); } }4.2 常见问题排查指南问题现象可能原因解决方案观察者未收到通知1. 未正确注册观察者2. notifyObservers未被调用1. 检查注册流程2. 添加调试输出确认通知触发内存泄漏1. 观察者未正确注销2. 循环引用1. 使用weak_ptr代替shared_ptr2. 确保析构时注销多线程崩溃1. 竞态条件2. 迭代器失效1. 添加互斥锁保护2. 通知前复制观察者列表4.3 设计模式组合应用观察者模式常与其他模式配合使用与工厂模式结合创建不同类型的观察者std::unique_ptrObserver createObserver(ObserverType type) { switch(type) { case DISPLAY: return std::make_uniqueDisplayObserver(); case LOGGER: return std::make_uniqueLoggerObserver(); // ... } }与装饰器模式结合为观察者添加额外功能class TimestampDecorator : public Observer { private: std::unique_ptrObserver wrapped; public: explicit TimestampDecorator(std::unique_ptrObserver o) : wrapped(std::move(o)) {} void update(float t, float h, float p) override { std::cout [ getCurrentTime() ] ; wrapped-update(t, h, p); } };5. 测试与验证策略5.1 单元测试示例使用Google Test框架验证观察者模式TEST(ObserverPatternTest, NotificationTest) { WeatherData weather; MockObserver mock; weather.registerObserver(mock); EXPECT_CALL(mock, update(25.5, 65, 1013.25)).Times(1); weather.setMeasurements(25.5, 65, 1013.25); } TEST(ObserverPatternTest, DeregistrationTest) { WeatherData weather; MockObserver mock; weather.registerObserver(mock); weather.removeObserver(mock); EXPECT_CALL(mock, update(_, _, _)).Times(0); weather.setMeasurements(25.5, 65, 1013.25); }5.2 性能测试要点测量不同观察者数量下的通知延迟测试多线程环境下的稳定性验证内存使用情况确保无泄漏TEST(ObserverPatternTest, PerformanceTest) { WeatherData weather; const int kObservers 10000; std::vectorstd::unique_ptrObserver observers; for (int i 0; i kObservers; i) { observers.push_back(std::make_uniqueSimpleObserver()); weather.registerObserver(observers.back().get()); } auto start std::chrono::high_resolution_clock::now(); weather.setMeasurements(25.0, 60, 1013); auto end std::chrono::high_resolution_clock::now(); std::cout Notification time: std::chrono::duration_caststd::chrono::microseconds(end - start).count() μs\n; }在最近的一个工业物联网项目中我们使用观察者模式处理传感器网络数据。当采用批量更新策略后系统吞吐量提升了3倍。关键点在于找到合适的batch size——太小影响性能太大导致响应延迟。经过压力测试最终确定50个更新/批次是最佳平衡点。

相关推荐

2026年配音工具技术选型:长文本能力与API集成度的权衡分析
2026年配音工具技术选型:长文本能力与API集成度的权衡分析

做技术内容这两年,配音环节换过不少工具。从自录音频到AI合成,踩过的坑涵盖长文本生成中断、多音字误读、免费版带水印、缺乏API集成接口等。前后测了十来款,结合桌面剪辑、移动端批量、程序化调用等场景,把2026年实测可用的方案整… · 2026/9/22 22:06:38

2026最新哪些是蓝筹股?面试突击:代码跑不通咋调
2026最新哪些是蓝筹股?面试突击:代码跑不通咋调

2026最新哪些是蓝筹股?面试突击:代码跑不通咋调 刚把掘金技术社区里那篇爆火的蓝筹股筛选代码复制到本地,IDE 直接飘红,报错信息像天书一样看不懂。这种“复制即崩溃”的绝望感,是不是你也正经历着?别慌,这不只是代码的问题,更是你面试前准备… · 2026/9/22 22:06:38

瓜五笔怎么打:3个避坑点+最佳实践助你通关
瓜五笔怎么打:3个避坑点+最佳实践助你通关

瓜五笔怎么打:3个避坑点+最佳实践助你通关 官方文档翻了三遍还是觉得云里雾里?别急,这太正常了。很多新人一上来就啃几十页的规范,结果重点全漏了。其实,“瓜五笔怎么打”这类高频面试题,核心就三点:拆字逻辑、词组规则、易错点。今天我用10年实战… · 2026/9/22 22:06:32

搞定迅雷代理下载源码,面试必问的底层逻辑全在这
搞定迅雷代理下载源码,面试必问的底层逻辑全在这

搞定迅雷代理下载源码,面试必问的底层逻辑全在这 版本升级后 API 全变了,以前写的代码直接报错?这不仅是开发者的噩梦,也是 面试必问… · 2026/9/23 1:01:40

3步搞定cad绘图练习:源码解析助你搞定实战项目
3步搞定cad绘图练习:源码解析助你搞定实战项目

3步搞定cad绘图练习:源码解析助你搞定实战项目 屏幕又黑了?刚运行完那个 dwg2svg 脚本,终端里刷满了 TypeError: Cannot read property 'x' of undefined ,下面还跟着几十行红色的… · 2026/9/23 1:01:28

sdsz性能优化实录:新手避坑指南,告别配置卡半天
sdsz性能优化实录:新手避坑指南,告别配置卡半天

sdsz性能优化实录:新手避坑指南,告别配置卡半天 刚接触 sdsz 开发时,你是不是也经历过这种绝望时刻?环境配置就卡半天,依赖装不上,版本冲突报错满天飞,查文档像大海捞针。别慌,这正是新手最容易掉进的坑。在掘金技术社区翻了不少帖子,发现… · 2026/9/23 1:00:51

3个坑让钱选代码跑不通?这份高频面试题实战指南帮你搞定
3个坑让钱选代码跑不通?这份高频面试题实战指南帮你搞定

3个坑让钱选代码跑不通?这份高频面试题实战指南帮你搞定 昨晚还在改那个该死的 MoneySelect 模块,复制了一段网上流传很广的 Python 示例,结果一运行直接报 AttributeError… · 2026/9/23 1:00:45

魔兽板甲幻化避坑指南:3个底层逻辑搞定高频面试题
魔兽板甲幻化避坑指南:3个底层逻辑搞定高频面试题

魔兽板甲幻化避坑指南:3个底层逻辑搞定高频面试题 官方文档那一堆术语看三遍还是云里雾里?别慌,这就是典型的“信息过载”陷阱。很多玩家在折腾魔兽板甲幻化时,卡在“为什么这套装备不能换”或者“为什么颜色对不上”的死胡同里,其实核心就三个底层逻辑… · 2026/9/23 1:00:39

1q币等于多少q点?面试必问的换算逻辑与代码实战
1q币等于多少q点?面试必问的换算逻辑与代码实战

1q币等于多少q点?面试必问的换算逻辑与代码实战 版本升级后 API 全变了,这是很多开发者在接手旧项目时的噩梦。特别是在处理支付网关或虚拟币转换时,底层的数值精度处理稍有不慎,资金对账就会出错。今天我们要聊的 1q币等于多少q点… · 2026/9/23 1:00:21

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码