参考文章
代码-图
#include <stdio.h>
// 前置声明
typedef struct _subject_t subject_t;
typedef struct _observer_t observer_t;
void subject_attach(subject_t *sub, observer_t *obs);
void subject_detach(subject_t *sub, observer_t *obs);
typedef struct _observer_t
{
void (*update)(struct _observer_t*, int, int); // 修正函数指针语法
struct _observer_t *next;
struct _subject_t *subject; // 添加前向声明
} observer_t;
void observer_init(observer_t *obs, subject_t *subject)
{
obs->update = NULL;
obs->next = NULL;
obs->subject= subject;
subject_attach(subject, obs);
}
void observer_deinit(observer_t *obs) // 移除多余参数
{
subject_detach(obs->subject, obs);
}
void observer_update(observer_t *obs, int temp, int hum)
{
if (obs->update != NULL)
{
obs->update(obs, temp, hum);
}
}
// subject_t 结构定义示例
struct _subject_t {
observer_t *obs;
};
// 静态初始化主题对象
void subject_init(subject_t *subject)
{
subject->obs = NULL;
}
// 需要实现的观察者注册函数
void subject_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;
}
}
// 需要实现的观察者注销函数
void subject_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;
}
}
// 数据通知订阅者
void subject_notify(subject_t *subject, int temp, int hum)
{
observer_t *_obs = subject->obs;
while(_obs)
{
observer_update(_obs, temp, hum);
_obs = _obs->next;
}
}
typedef struct _display_t
{
observer_t obs;
} display_t;
void display_show(observer_t *obs, int temp, int hum);
// 静态构造函数
void display_init(display_t *display, subject_t *subject)
{
observer_init(&display->obs, subject);
display->obs.update = display_show;
}
// 模拟屏幕显示
void display_show(observer_t *obs, int temp, int hum)
{
printf("temp:%d\n", temp);
printf("hum:%d\n", hum);
}
typedef struct _ble_t
{
observer_t obs;
} ble_t;
void ble_send(observer_t *obs, int temp, int hum);
// 静态构造函数(修正变量名错误)
void ble_init(ble_t* ble, subject_t *subject)
{
observer_init(&ble->obs, subject); // 修正 display-> → ble->
ble->obs.update = ble_send;
}
// 模拟蓝牙发送
void ble_send(observer_t *obs, int temp, int hum)
{
printf("send temp:%d\n", temp);
printf("send hum:%d\n", hum);
}
typedef struct _sensor_t
{
subject_t subject;
int temp;
int hum;
} sensor_t;
// 静态构造函数
void sensor_init(sensor_t *sensor)
{
sensor->temp = 0;
sensor->hum = 0;
subject_init(&sensor->subject);
}
// 模拟温湿度传感器读取数值
void sensor_get(sensor_t *sensor, int temp, int hum)
{
sensor->temp = temp;
sensor->hum = hum;
subject_notify(&sensor->subject, temp, hum); // 更安全的指针转换
}
int main()
{
// 创建传感器实例
sensor_t my_sensor;
sensor_init(&my_sensor);
// 创建显示设备和蓝牙设备
display_t screen;
display_init(&screen, (subject_t*)&my_sensor);
ble_t ble_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);
return 0;
}
想法
观察者通常是应用中上层的,而被观察对象通常是下层。
被观察者通过notify方法将数据上传到观察者端,这个过程类似“回调函数”的作用,下层应用通知(触发)上层应用;下层(被观察者)仅通过抽象接口(如Observer.update())通知上层,不依赖具体实现;
观察者模式的notify可通知多个观察者(一对多),而回调通常是一对一的关系
观察者模式通过中介(如Subject类)完全解耦双方;回调中调用方需持有被调用方的引用(如Callback接口实例)
观察者模式对比订阅发布模式
维度 | 观察者模式 (Observer Pattern) | 发布-订阅模式 (Pub-Sub Pattern) |
---|---|---|
耦合度 | 松耦合(观察者直接依赖被观察者,被观察者维护观察者列表) | 完全解耦(通过中介传递消息,发布者和订阅者互不知晓,依赖中介) |
通信方式 | 同步调用(立即触发) | 异步分发(通过消息队列/事件总线) |
核心角色 | 被观察者(Subject) + 观察者(Observer) | 发布者(Publisher) + 订阅者(Subscriber) + 中介(Broker) |
交互方式 | 被观察者直接调用观察者的方法 | 发布者和订阅者通过主题(Topic)间接通信 |
性能影响 | 可能阻塞(观察者处理耗时) | 高吞吐(支持削峰填谷) |
典型应用场景 | GUI事件、单应用组件联动 | 微服务通信、分布式系统事件通知 |
代码实现复杂度 | 简单(需手动维护观察者列表) | 较复杂(需引入中介系统) |
经典案例 | Java Observable /Observer |
Redis PUB/SUB、Kafka、RabbitMQ |
扩展性 | 低(新增观察者需修改被观察者) | 高(动态增减订阅者无需修改发布者) |
适用规模 | 小型系统或模块内部,适合对象间有明确依赖关系、需要实时同步响应的场景(如UI更新、状态同步) | 中大型系统或跨服务通信,适合需要解耦、异步处理或跨系统通信的场景(如微服务、分布式日志) |
依赖倒置原则(Dependency Inversion Principle, DIP)
其核心思想是通过抽象解耦高层模块与低层模块的依赖关系,提升系统的灵活性、可维护性和扩展性。
依赖倒置原则的定义
高层模块不应依赖低层模块:两者都应依赖于抽象(接口或抽象类)。
抽象不应依赖细节:抽象层定义规范,具体实现(细节)应依赖抽象
核心目标:通过面向接口编程,减少模块间的直接耦合,使系统更适应变化。
为什么需要依赖倒置?
传统设计的缺陷:
高层模块直接调用低层模块,导致低层改动时高层必须同步修改(如更换数据库需重写业务逻辑)
代码复用性差,并行开发困难(如团队需等待底层模块完成才能开发高层)
依赖倒置的优势:
解耦:高层模块通过抽象接口调用低层功能,低层实现可自由替换
扩展性:新增功能只需实现接口,无需修改现有代码(符合开闭原则)
测试友好:可通过Mock对象模拟接口,独立测试高层逻辑