观察者模式(Observer Pattern)的核心是建立对象间的一对多依赖关系 :当一个对象(主题)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。在C语言中,可以通过主题维护观察者列表+观察者实现统一更新接口实现:主题存储观察者指针,状态变化时遍历列表调用观察者的更新函数。
C语言实现观察者模式的思路
- 抽象观察者(Observer):定义观察者的统一更新接口(函数指针),用于接收主题通知。
- 具体观察者(Concrete Observer):实现更新接口,根据主题状态变化执行具体操作。
- 抽象主题(Subject):定义主题的接口(添加观察者、移除观察者、通知所有观察者),并维护观察者列表。
- 具体主题(Concrete Subject):存储状态,状态变化时调用通知方法。
示例:气象站(主题)与多个显示器(观察者)
气象站(主题)会定期更新温度、湿度数据,多个显示器(如控制台显示器、图形显示器)作为观察者,实时显示最新数据。
步骤1:定义抽象观察者接口
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 前向声明主题,避免循环依赖
typedef struct Subject Subject;
// 抽象观察者:定义更新接口(接收主题的最新状态)
typedef struct Observer {
// 更新方法:参数为主题(可主动获取状态)或直接传入状态
void (*update)(struct Observer* self, Subject* subject);
// 销毁观察者
void (*destroy)(struct Observer* self);
} Observer;
步骤2:定义抽象主题接口
主题需维护观察者列表,并提供添加、移除、通知观察者的方法。
c
// 观察者节点:用于链表存储观察者
typedef struct ObserverNode {
Observer* observer;
struct ObserverNode* next;
} ObserverNode;
// 抽象主题:气象站(维护观察者列表)
typedef struct Subject {
float temperature; // 温度(状态)
float humidity; // 湿度(状态)
ObserverNode* observers; // 观察者链表
// 主题接口
void (*attach)(struct Subject* self, Observer* observer); // 添加观察者
void (*detach)(struct Subject* self, Observer* observer); // 移除观察者
void (*notify)(struct Subject* self); // 通知所有观察者
// 获取状态(供观察者调用)
void (*get_state)(struct Subject* self, float* temp, float* humi);
} Subject;
步骤3:实现具体主题(气象站)
具体主题实现主题接口,状态变化时触发通知。
c
// 具体主题:气象站
typedef struct {
Subject subject; // 继承抽象主题
} WeatherStation;
// 添加观察者(链表头插)
static void weather_attach(Subject* self, Observer* observer) {
if (!observer) return;
ObserverNode* node = (ObserverNode*)malloc(sizeof(ObserverNode));
if (!node) return;
node->observer = observer;
node->next = self->observers; // 插入链表头部
self->observers = node;
printf("添加观察者成功\n");
}
// 移除观察者(从链表中删除)
static void weather_detach(Subject* self, Observer* observer) {
if (!observer || !self->observers) return;
ObserverNode* prev = NULL;
ObserverNode* curr = self->observers;
while (curr) {
if (curr->observer == observer) {
if (prev) {
prev->next = curr->next; // 前节点指向后节点
} else {
self->observers = curr->next; // 更新头节点
}
free(curr); // 释放节点
printf("移除观察者成功\n");
return;
}
prev = curr;
curr = curr->next;
}
}
// 通知所有观察者(遍历链表调用update)
static void weather_notify(Subject* self) {
ObserverNode* curr = self->observers;
while (curr) {
curr->observer->update(curr->observer, self); // 调用观察者的更新方法
curr = curr->next;
}
}
// 提供状态获取接口(观察者通过此方法获取最新数据)
static void weather_get_state(Subject* self, float* temp, float* humi) {
*temp = self->temperature;
*humi = self->humidity;
}
// 气象站状态更新(模拟传感器数据变化)
void weather_station_update(WeatherStation* station, float temp, float humi) {
station->subject.temperature = temp;
station->subject.humidity = humi;
printf("\n气象站数据更新:温度=%.1f℃,湿度=%.1f%%\n", temp, humi);
station->subject.notify((Subject*)station); // 状态变化,通知所有观察者
}
// 创建气象站(初始化主题)
WeatherStation* weather_station_create() {
WeatherStation* station = (WeatherStation*)malloc(sizeof(WeatherStation));
if (!station) return NULL;
// 初始化主题状态
station->subject.temperature = 0;
station->subject.humidity = 0;
station->subject.observers = NULL;
// 绑定主题接口方法
station->subject.attach = weather_attach;
station->subject.detach = weather_detach;
station->subject.notify = weather_notify;
station->subject.get_state = weather_get_state;
return station;
}
// 销毁气象站(释放观察者链表)
void weather_station_destroy(WeatherStation* station) {
if (!station) return;
ObserverNode* curr = station->subject.observers;
while (curr) {
ObserverNode* temp = curr;
curr = curr->next;
temp->observer->destroy(temp->observer); // 销毁观察者
free(temp); // 销毁节点
}
free(station);
}
步骤4:实现具体观察者(显示器)
多个观察者实现update方法,接收主题通知并更新显示。
4.1 控制台显示器
c
// 具体观察者1:控制台显示器
typedef struct {
Observer observer; // 继承观察者接口
char name[32]; // 显示器名称
} ConsoleDisplay;
// 控制台显示器的更新方法:获取主题状态并打印
static void console_update(Observer* self, Subject* subject) {
ConsoleDisplay* display = (ConsoleDisplay*)self;
float temp, humi;
subject->get_state(subject, &temp, &humi); // 从主题获取最新状态
printf("[%s] 显示:温度=%.1f℃,湿度=%.1f%%\n", display->name, temp, humi);
}
// 控制台显示器的销毁
static void console_destroy(Observer* self) {
free(self);
}
// 创建控制台显示器
Observer* console_display_create(const char* name) {
ConsoleDisplay* display = (ConsoleDisplay*)malloc(sizeof(ConsoleDisplay));
if (!display) return NULL;
// 绑定观察者接口
display->observer.update = console_update;
display->observer.destroy = console_destroy;
strncpy(display->name, name, sizeof(display->name)-1);
return (Observer*)display;
}
4.2 图形显示器(模拟)
c
// 具体观察者2:图形显示器(简化实现)
typedef struct {
Observer observer;
char name[32];
} GraphicDisplay;
// 图形显示器的更新方法
static void graphic_update(Observer* self, Subject* subject) {
GraphicDisplay* display = (GraphicDisplay*)self;
float temp, humi;
subject->get_state(subject, &temp, &humi);
printf("[%s] 绘制图表:温度=%.1f℃,湿度=%.1f%%\n", display->name, temp, humi);
}
// 图形显示器的销毁
static void graphic_destroy(Observer* self) {
free(self);
}
// 创建图形显示器
Observer* graphic_display_create(const char* name) {
GraphicDisplay* display = (GraphicDisplay*)malloc(sizeof(GraphicDisplay));
if (!display) return NULL;
display->observer.update = graphic_update;
display->observer.destroy = graphic_destroy;
strncpy(display->name, name, sizeof(display->name)-1);
return (Observer*)display;
}
步骤5:使用观察者模式
主题(气象站)添加观察者(显示器),状态变化时自动通知所有观察者。
c
int main() {
// 1. 创建主题(气象站)
WeatherStation* station = weather_station_create();
if (!station) return 1;
// 2. 创建观察者(显示器)
Observer* console1 = console_display_create("客厅控制台");
Observer* graphic1 = graphic_display_create("卧室图形屏");
// 3. 主题添加观察者
station->subject.attach((Subject*)station, console1);
station->subject.attach((Subject*)station, graphic1);
// 4. 模拟气象站数据更新(触发通知)
weather_station_update(station, 25.5f, 60.0f);
weather_station_update(station, 26.0f, 58.5f);
// 5. 移除一个观察者
station->subject.detach((Subject*)station, console1);
printf("\n移除控制台后的数据更新:\n");
weather_station_update(station, 24.8f, 62.0f);
// 6. 清理资源
weather_station_destroy(station);
return 0;
}
输出结果
添加观察者成功
添加观察者成功
气象站数据更新:温度=25.5℃,湿度=60.0%
[客厅控制台] 显示:温度=25.5℃,湿度=60.0%
[卧室图形屏] 绘制图表:温度=25.5℃,湿度=60.0%
气象站数据更新:温度=26.0℃,湿度=58.5%
[客厅控制台] 显示:温度=26.0℃,湿度=58.5%
[卧室图形屏] 绘制图表:温度=26.0℃,湿度=58.5%
移除观察者成功
移除控制台后的数据更新:
气象站数据更新:温度=24.8℃,湿度=62.0%
[卧室图形屏] 绘制图表:温度=24.8℃,湿度=62.0%
核心思想总结
- 松耦合:主题与观察者通过接口交互,主题无需知道观察者的具体类型,新增/移除观察者无需修改主题代码(符合开放-封闭原则)。
- 自动通知:主题状态变化时,所有注册的观察者会被自动通知,无需手动调用更新。
- 一对多依赖:一个主题可对应多个观察者(如气象站同时连接多个显示器),观察者之间互不影响。
C语言通过链表维护观察者列表,结合函数指针实现更新接口,完美模拟了观察者模式的核心,适合实现事件监听、状态同步等场景(如GUI事件、传感器数据分发、消息订阅系统)。