观察者模式完全解析:从紧耦合轮询到松耦合的事件通知
解析观察者模式。
要求:UML建模,Markdown,mermind,项目文件结构,具体模块实现。
实例可以是:气象站(温度、湿度传感器)通知多个显示设备;或者GUI按钮事件;或者消息发布订阅。选择简单易懂的:气象站(Subject)更新数据,多个观察者(手机App、网页显示器、报警器)接收更新。
实现C语言版本,使用函数指针链表模拟观察者列表。
观察者模式完全解析:从紧耦合轮询到松耦合的事件通知
1. 核心问题:如何让多个对象自动响应另一个对象的状态变化?
在软件系统中,经常遇到一个对象的状态变化需要通知其他多个对象的场景(例如:气象站温度变化时,多个显示屏需要更新;股票价格变动时,多个投资者收到通知)。传统的做法是让被观察者直接调用观察者的方法,导致被观察者与观察者紧耦合,难以扩展。
不推荐的做法:被观察者硬编码调用所有观察者:
void weather_data_changed(float temp, float humidity) {
// 每次增加新的观察者都要修改这个函数
phone_app_update(temp, humidity);
web_display_update(temp, humidity);
alarm_update(temp, humidity);
// 新增LED显示屏?又要加一行...
}
观察者模式(Observer Pattern) 定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。它将被观察者(Subject)与观察者(Observer)解耦,被观察者只需维护一个观察者列表,无需知道观察者的具体类型。
2. 观察者模式 UML 建模(以气象站为例)
2.1 经典观察者模式类图
2.2 嵌入式C语言实现结构(函数指针链表)
3. 实例:气象站数据推送系统
3.1 需求描述
设计一个气象站系统,当温湿度数据变化时,需要通知多个显示设备:
- 手机App显示:显示当前温湿度。
- 网页显示器:显示温湿度及时间戳。
- 高温报警器:当温度超过30度时发出警报。
要求可以动态添加/删除观察者,被观察者无需修改代码即可支持新设备。
3.2 项目文件结构
observer_pattern/
├── main.c # 客户端
├── subject.h # 被观察者抽象接口
├── observer.h # 观察者抽象接口
├── weather_data.c # 具体被观察者(气象站)
├── weather_data.h
├── phone_display.c # 具体观察者(手机App)
├── phone_display.h
├── web_display.c # 具体观察者(网页显示器)
├── web_display.h
├── alarm.c # 具体观察者(高温报警器)
├── alarm.h
├── Makefile
└── README.md
3.3 基础接口定义
观察者接口(observer.h)
// observer.h
#ifndef OBSERVER_H
#define OBSERVER_H
// 前向声明(避免循环引用)
struct Subject;
// 观察者接口(函数指针结构体)
typedef struct Observer {
void (*update)(struct Observer* self, struct Subject* subject);
struct Observer* next; // 用于链表
} Observer;
#endif
被观察者接口(subject.h)
// subject.h
#ifndef SUBJECT_H
#define SUBJECT_H
#include "observer.h"
// 被观察者接口(抽象类)
typedef struct Subject {
Observer* observer_head;
void (*attach)(struct Subject* self, Observer* observer);
void (*detach)(struct Subject* self, Observer* observer);
void (*notify)(struct Subject* self);
} Subject;
// 公共的 attach/detach/notify 实现(供子类调用)
void Subject_Init(Subject* self);
void Subject_Attach(Subject* self, Observer* observer);
void Subject_Detach(Subject* self, Observer* observer);
void Subject_Notify(Subject* self);
#endif
// subject.c
#include "subject.h"
#include <stdlib.h>
#include <stdio.h>
void Subject_Init(Subject* self) {
self->observer_head = NULL;
self->attach = Subject_Attach;
self->detach = Subject_Detach;
self->notify = Subject_Notify;
}
void Subject_Attach(Subject* self, Observer* observer) {
// 添加到链表头部
observer->next = self->observer_head;
self->observer_head = observer;
printf("[Subject] Observer attached.\n");
}
void Subject_Detach(Subject* self, Observer* observer) {
Observer** curr = &self->observer_head;
while (*curr) {
if (*curr == observer) {
*curr = observer->next;
printf("[Subject] Observer detached.\n");
return;
}
curr = &(*curr)->next;
}
}
void Subject_Notify(Subject* self) {
Observer* curr = self->observer_head;
while (curr) {
curr->update(curr, self);
curr = curr->next;
}
}
3.4 具体被观察者:WeatherData
// weather_data.h
#ifndef WEATHER_DATA_H
#define WEATHER_DATA_H
#include "subject.h"
typedef struct {
Subject base;
float temperature;
float humidity;
} WeatherData;
void WeatherData_Init(WeatherData* self);
void WeatherData_SetMeasurements(WeatherData* self, float temp, float humidity);
float WeatherData_GetTemperature(WeatherData* self);
float WeatherData_GetHumidity(WeatherData* self);
#endif
// weather_data.c
#include "weather_data.h"
#include <stdio.h>
void WeatherData_Init(WeatherData* self) {
Subject_Init(&self->base);
self->temperature = 0.0f;
self->humidity = 0.0f;
}
void WeatherData_SetMeasurements(WeatherData* self, float temp, float humidity) {
printf("\n[WeatherData] New measurements: temp=%.1f°C, humidity=%.1f%%\n", temp, humidity);
self->temperature = temp;
self->humidity = humidity;
self->base.notify(&self->base); // 通知所有观察者
}
float WeatherData_GetTemperature(WeatherData* self) {
return self->temperature;
}
float WeatherData_GetHumidity(WeatherData* self) {
return self->humidity;
}
3.5 具体观察者实现
手机App显示(phone_display.h / .c)
// phone_display.h
#ifndef PHONE_DISPLAY_H
#define PHONE_DISPLAY_H
#include "observer.h"
typedef struct {
Observer base;
int id;
} PhoneDisplay;
PhoneDisplay* PhoneDisplay_Create(int id);
void PhoneDisplay_Destroy(PhoneDisplay* display);
#endif
// phone_display.c
#include "phone_display.h"
#include "weather_data.h"
#include <stdio.h>
#include <stdlib.h>
static void phone_update(Observer* self, struct Subject* subject) {
PhoneDisplay* display = (PhoneDisplay*)self;
WeatherData* weather = (WeatherData*)subject; // 向下转型
printf("[PhoneDisplay %d] Current weather: %.1f°C, %.1f%%\n",
display->id,
WeatherData_GetTemperature(weather),
WeatherData_GetHumidity(weather));
}
PhoneDisplay* PhoneDisplay_Create(int id) {
PhoneDisplay* d = (PhoneDisplay*)malloc(sizeof(PhoneDisplay));
if (!d) return NULL;
d->base.update = phone_update;
d->base.next = NULL;
d->id = id;
return d;
}
void PhoneDisplay_Destroy(PhoneDisplay* display) {
free(display);
}
网页显示器(web_display.h / .c)
// web_display.h
#ifndef WEB_DISPLAY_H
#define WEB_DISPLAY_H
#include "observer.h"
typedef struct {
Observer base;
} WebDisplay;
WebDisplay* WebDisplay_Create(void);
void WebDisplay_Destroy(WebDisplay* display);
#endif
// web_display.c
#include "web_display.h"
#include "weather_data.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static void web_update(Observer* self, struct Subject* subject) {
WeatherData* weather = (WeatherData*)subject;
time_t now = time(NULL);
printf("[WebDisplay] %s - Temp: %.1f°C, Humidity: %.1f%%\n",
ctime(&now),
WeatherData_GetTemperature(weather),
WeatherData_GetHumidity(weather));
}
WebDisplay* WebDisplay_Create(void) {
WebDisplay* d = (WebDisplay*)malloc(sizeof(WebDisplay));
if (!d) return NULL;
d->base.update = web_update;
d->base.next = NULL;
return d;
}
void WebDisplay_Destroy(WebDisplay* display) {
free(display);
}
高温报警器(alarm.h / .c)
// alarm.h
#ifndef ALARM_H
#define ALARM_H
#include "observer.h"
typedef struct {
Observer base;
float threshold;
} Alarm;
Alarm* Alarm_Create(float threshold);
void Alarm_Destroy(Alarm* alarm);
#endif
// alarm.c
#include "alarm.h"
#include "weather_data.h"
#include <stdio.h>
#include <stdlib.h>
static void alarm_update(Observer* self, struct Subject* subject) {
Alarm* alarm = (Alarm*)self;
WeatherData* weather = (WeatherData*)subject;
float temp = WeatherData_GetTemperature(weather);
if (temp > alarm->threshold) {
printf("[ALARM] High temperature detected: %.1f°C (threshold=%.1f)!\n",
temp, alarm->threshold);
} else {
printf("[ALARM] Temperature normal: %.1f°C\n", temp);
}
}
Alarm* Alarm_Create(float threshold) {
Alarm* a = (Alarm*)malloc(sizeof(Alarm));
if (!a) return NULL;
a->base.update = alarm_update;
a->base.next = NULL;
a->threshold = threshold;
return a;
}
void Alarm_Destroy(Alarm* alarm) {
free(alarm);
}
3.6 客户端代码(main.c)
// main.c
#include <stdio.h>
#include "weather_data.h"
#include "phone_display.h"
#include "web_display.h"
#include "alarm.h"
int main(void) {
printf("=== Observer Pattern Demo: Weather Station ===\n");
// 创建被观察者(气象站)
WeatherData weatherStation;
WeatherData_Init(&weatherStation);
// 创建观察者
PhoneDisplay* phone1 = PhoneDisplay_Create(1);
PhoneDisplay* phone2 = PhoneDisplay_Create(2);
WebDisplay* web = WebDisplay_Create();
Alarm* alarm = Alarm_Create(30.0f);
// 注册观察者
weatherStation.base.attach(&weatherStation.base, (Observer*)phone1);
weatherStation.base.attach(&weatherStation.base, (Observer*)phone2);
weatherStation.base.attach(&weatherStation.base, (Observer*)web);
weatherStation.base.attach(&weatherStation.base, (Observer*)alarm);
// 模拟数据变化
WeatherData_SetMeasurements(&weatherStation, 25.0f, 65.0f);
WeatherData_SetMeasurements(&weatherStation, 32.5f, 70.0f);
WeatherData_SetMeasurements(&weatherStation, 28.0f, 55.0f);
// 移除一个观察者(例如 phone2)
printf("\n--- Removing PhoneDisplay 2 ---\n");
weatherStation.base.detach(&weatherStation.base, (Observer*)phone2);
// 再次更新
WeatherData_SetMeasurements(&weatherStation, 26.5f, 60.0f);
// 清理资源
PhoneDisplay_Destroy(phone1);
PhoneDisplay_Destroy(phone2);
WebDisplay_Destroy(web);
Alarm_Destroy(alarm);
return 0;
}
3.7 编译与运行(Makefile)
CC = gcc
CFLAGS = -Wall -g
OBJS = main.o subject.o weather_data.o phone_display.o web_display.o alarm.o
all: observer_demo
observer_demo: $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o observer_demo
运行输出示例:
=== Observer Pattern Demo: Weather Station ===
[Subject] Observer attached.
[Subject] Observer attached.
[Subject] Observer attached.
[Subject] Observer attached.
[WeatherData] New measurements: temp=25.0°C, humidity=65.0%
[PhoneDisplay 1] Current weather: 25.0°C, 65.0%
[PhoneDisplay 2] Current weather: 25.0°C, 65.0%
[WebDisplay] Mon Apr 10 10:00:00 2025
- Temp: 25.0°C, Humidity: 65.0%
[ALARM] Temperature normal: 25.0°C
[WeatherData] New measurements: temp=32.5°C, humidity=70.0%
[PhoneDisplay 1] Current weather: 32.5°C, 70.0%
[PhoneDisplay 2] Current weather: 32.5°C, 70.0%
[WebDisplay] Mon Apr 10 10:00:05 2025
- Temp: 32.5°C, Humidity: 70.0%
[ALARM] High temperature detected: 32.5°C (threshold=30.0)!
--- Removing PhoneDisplay 2 ---
[Subject] Observer detached.
[WeatherData] New measurements: temp=26.5°C, humidity=60.0%
[PhoneDisplay 1] Current weather: 26.5°C, 60.0%
[WebDisplay] Mon Apr 10 10:00:10 2025
- Temp: 26.5°C, Humidity: 60.0%
[ALARM] Temperature normal: 26.5°C
4. 深入解析设计要点
4.1 观察者模式的核心组成
- 被观察者(Subject):
WeatherData,维护观察者列表,提供注册/注销方法,状态变化时通知所有观察者。 - 观察者(Observer):
PhoneDisplay,WebDisplay,Alarm,实现update方法,接收通知并做出响应。 - 客户端:创建被观察者和观察者,建立订阅关系。
4.2 观察者模式如何实现松耦合
- 被观察者只依赖抽象的
Observer接口,不知道具体观察者类型。 - 观察者可以动态地注册或注销,不影响被观察者。
- 增加新的观察者(如LED屏幕)只需实现
Observer接口,无需修改被观察者。
4.3 推模型 vs 拉模型
- 推模型:被观察者将状态数据作为参数传递给观察者(本例是拉模型)。
- 拉模型:观察者主动从被观察者获取所需数据(本例使用
WeatherData_GetTemperature等)。拉模型更灵活,观察者可以选择需要的数据。
4.4 嵌入式环境中的注意事项
- 内存管理:观察者链表使用动态分配,嵌入式系统可改用静态数组或预分配节点池。
- 通知顺序:链表顺序决定通知顺序,通常无所谓。
- 线程安全:在多线程环境中,需要对观察者列表加锁。
- 避免在通知中修改列表:可能导致迭代器失效。可以在通知前复制列表或使用标记。
4.5 与其它模式的关系
- 观察者模式 + 中介者模式:中介者可以管理复杂的观察者关系。
- 观察者模式 + 策略模式:观察者的更新算法可以使用策略模式。
5. 总结
观察者模式通过 抽象依赖 实现了对象间的松耦合事件通知。其核心价值:
- 解耦:被观察者无需知道观察者的具体实现。
- 动态性:可以在运行时添加或删除观察者。
- 可扩展:增加新观察者不影响现有代码。
在C语言中,使用函数指针和链表即可实现观察者模式,广泛应用于事件驱动系统、GUI、消息通知等场景。
一句话记住:
“订阅一次,自动通知;观察者模式,松耦合利器。” —— 观察者模式
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)