解析观察者模式。

要求: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 经典观察者模式类图

notifies

«interface»

Subject

+attach(Observer)

+detach(Observer)

+notify()

WeatherData

-observers: List<Observer>

-temperature: float

-humidity: float

+attach()

+detach()

+notify()

+setMeasurements()

+getTemperature()

+getHumidity()

«interface»

Observer

+update()

PhoneDisplay

+update()

WebDisplay

+update()

Alarm

+update()

2.2 嵌入式C语言实现结构(函数指针链表)

«struct»

Observer

+update: func

+next: Observer

Subject

-head: Observer

+attach(observer)

+detach(observer)

+notify()

WeatherData

+base: Subject

-temp, humidity

+setMeasurements()

PhoneDisplay

+base: Observer

+update()

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、消息通知等场景。

一句话记住
“订阅一次,自动通知;观察者模式,松耦合利器。” —— 观察者模式

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐