【设计模式】观察者模式
参考文章
一文讲清楚观察者模式
代码-图
#include<stdio.h>// 前置声明typedefstruct_subject_tsubject_t;typedefstruct_observer_tobserver_t;voidsubject_attach(subject_t*sub,observer_t*obs);voidsubject_detach(subject_t*sub,observer_t*obs);typedefstruct_observer_t{void(*update)(struct_observer_t*,int,int);// 修正函数指针语法struct_observer_t*next;struct_subject_t*subject;// 添加前向声明}observer_t;voidobserver_init(observer_t*obs,subject_t*subject){obs->update=NULL;obs->next=NULL;obs->subject=subject;subject_attach(subject,obs);}voidobserver_deinit(observer_t*obs)// 移除多余参数{subject_detach(obs->subject,obs);}voidobserver_update(observer_t*obs,inttemp,inthum){if(obs->update!=NULL){obs->update(obs,temp,hum);}}// subject_t 结构定义示例struct_subject_t{observer_t*obs;};// 静态初始化主题对象voidsubject_init(subject_t*subject){subject->obs=NULL;}// 需要实现的观察者注册函数voidsubject_attach(subject_t*subject,observer_t*obs){if(!subject->obs){subject->obs=obs;}else{observer_t*cur=subject->obs;while(cur->next)cur=cur->next;cur->next=obs;}}// 需要实现的观察者注销函数voidsubject_detach(subject_t*subject,observer_t*obs){observer_t**indirect=&subject->obs;// 使用二级指针简化操作while(*indirect){if(*indirect==obs){*indirect=obs->next;// 绕过目标节点obs->next=NULL;// 重置观察者的next指针return;}indirect=&(*indirect)->next;}}// 数据通知订阅者voidsubject_notify(subject_t*subject,inttemp,inthum){observer_t*_obs=subject->obs;while(_obs){observer_update(_obs,temp,hum);_obs=_obs->next;}}typedefstruct_display_t{observer_tobs;}display_t;voiddisplay_show(observer_t*obs,inttemp,inthum);// 静态构造函数voiddisplay_init(display_t*display,subject_t*subject){observer_init(&display->obs,subject);display->obs.update=display_show;}// 模拟屏幕显示voiddisplay_show(observer_t*obs,inttemp,inthum){printf("temp:%d\n",temp);printf("hum:%d\n",hum);}typedefstruct_ble_t{observer_tobs;}ble_t;voidble_send(observer_t*obs,inttemp,inthum);// 静态构造函数(修正变量名错误)voidble_init(ble_t*ble,subject_t*subject){observer_init(&ble->obs,subject);// 修正 display-> → ble->ble->obs.update=ble_send;}// 模拟蓝牙发送voidble_send(observer_t*obs,inttemp,inthum){printf("send temp:%d\n",temp);printf("send hum:%d\n",hum);}typedefstruct_sensor_t{subject_tsubject;inttemp;inthum;}sensor_t;// 静态构造函数voidsensor_init(sensor_t*sensor){sensor->temp=0;sensor->hum=0;subject_init(&sensor->subject);}// 模拟温湿度传感器读取数值voidsensor_get(sensor_t*sensor,inttemp,inthum){sensor->temp=temp;sensor->hum=hum;subject_notify(&sensor->subject,temp,hum);// 更安全的指针转换}intmain(){// 创建传感器实例sensor_tmy_sensor;sensor_init(&my_sensor);// 创建显示设备和蓝牙设备display_tscreen;display_init(&screen,(subject_t*)&my_sensor);ble_tble_device;ble_init(&ble_device,(subject_t*)&my_sensor);// 第一次数据更新(两个设备都应响应)printf("=== First Update ===\n");sensor_get(&my_sensor,25,60);// 移除蓝牙设备(通过反初始化)printf("\n=== Detaching BLE ===\n");observer_deinit(&ble_device.obs);// 第二次数据更新(仅显示设备响应)printf("\n=== Second Update ===\n");sensor_get(&my_sensor,26,58);return0;}想法
观察者模式是一种消息从下往上的方式,类似与回调函数,当下面发现了数据变化,通知上面。
应用层模块的输出通常是上层一步步往下,最终底层实现;
但是底层的数据想要往上传,1种通过上层的查询【数据拉模型】,2种是通过“通知/回调”的形式上传【数据推模型】
回调函数
【消费者】上层提供具体回调方法和数据入口;
【生产者】下层提供抽象回调方法,配置attach。
典型的就是串口数据接收;
观察者模式
观察者模式利用面向对象的方式,讲回调函数接口直接封装起来使用;
其中“观察者”通常是应用中上层的【消费者】,而“被观察对象”通常是下层【生成者】。
被观察者通过notify方法将数据上传到观察者端,这个过程类似“回调函数”的作用,下层应用通知(触发)上层应用;下层(被观察者)仅通过抽象接口(如Observer.update())通知上层,不依赖具体实现;
观察者模式的notify可通知多个观察者(一对多),而回调通常是一对一的关系
观察者模式通过中介(如Subject类)完全解耦双方;回调中调用方需持有被调用方的引用(如Callback接口实例)
观察者模式对比订阅发布模式
| 维度 | 观察者模式 (Observer Pattern) | 发布-订阅模式 (Pub-Sub Pattern) |
|---|---|---|
| 耦合度 | 松耦合(观察者直接依赖被观察者,被观察者维护观察者列表) | 完全解耦(通过中介传递消息,发布者和订阅者互不知晓,依赖中介) |
| 通信方式 | 同步调用(立即触发,回调的方式立刻执行) | 异步分发(通过消息队列/事件总线,先登记后处理) |
| 核心角色 | 被观察者(Subject) + 观察者(Observer) | 发布者(Publisher) + 订阅者(Subscriber) + 中介(Broker) |
| 交互方式 | 被观察者直接调用观察者的方法 | 发布者和订阅者通过主题(Topic)间接通信 |
| 性能影响 | 可能阻塞(观察者处理耗时) | 高吞吐(支持削峰填谷) |
| 典型应用场景 | GUI事件、单应用组件联动 | 微服务通信、分布式系统事件通知 |
| 代码实现复杂度 | 简单(需手动维护观察者列表) | 较复杂(需引入中介系统) |
| 经典案例 | JavaObservable/Observer | Redis PUB/SUB、Kafka、RabbitMQ |
| 扩展性 | 低(新增观察者需修改被观察者) | 高(动态增减订阅者无需修改发布者) |
| 适用规模 | 小型系统或模块内部,适合对象间有明确依赖关系、需要实时同步响应的场景(如UI更新、状态同步) | 中大型系统或跨服务通信,适合需要解耦、异步处理或跨系统通信的场景(如微服务、分布式日志) |
依赖倒置原则(Dependency Inversion Principle, DIP)
其核心思想是通过抽象解耦高层模块与低层模块的依赖关系,提升系统的灵活性、可维护性和扩展性。
依赖倒置原则的定义
高层模块不应依赖低层模块:两者都应依赖于抽象(接口或抽象类)。
抽象不应依赖细节:抽象层定义规范,具体实现(细节)应依赖抽象
核心目标:通过面向接口编程,减少模块间的直接耦合,使系统更适应变化。
为什么需要依赖倒置?
传统设计的缺陷:
高层模块直接调用低层模块,导致低层改动时高层必须同步修改(如更换数据库需重写业务逻辑)
代码复用性差,并行开发困难(如团队需等待底层模块完成才能开发高层)
依赖倒置的优势:
解耦:高层模块通过抽象接口调用低层功能,低层实现可自由替换
扩展性:新增功能只需实现接口,无需修改现有代码(符合开闭原则)
测试友好:可通过Mock对象模拟接口,独立测试高层逻辑
