【嵌入式解耦很难么】霸总:你们都要变成我的形状!

image.png

说在前面

在现代嵌入式软件结构中,解耦设计逐渐成为提升系统灵活性和可维护性的关键,而观察者模式正是一种有效实现这种设计理念的手段。这一次,我将从 C 语言的角度出发,基于上一章的面向对象,详细探讨如何构建一个高效且灵活的观察者系统,并通过实际案例展示这种模式在事件通知和状态更新中的应用。

举个 🌰

我们从一个案例开始,假设我们在开发一个家用温湿度传感器。这个产品可以接受实时温湿度。程序会将温湿度的数值显示在段码屏上。同时,这部分数据也会通过 BLE 发送到手机 APP 上。每当温度发生变化时,显示器及 APP 上,都会同时被更新。

Image
Image

最简单直接的思路

首先,我们创建一个 display 类,用于模拟显示温湿度

#include <stdio.h>

typedef struct _display_t 
{
    // 省略和底层相关的属性
} display_t;

// 静态构造函数
void display_init(display_t *display) 
{
    // 省略对底层相关属性初始化
}

// 模拟屏幕显示
void display_show(display_t *display, int temp, int hum) 
{
    printf("temp:%d\n", temp);
    printf("hum:%d\n", hum);
}

接下来, 我们创建一个 ble 类,用于模拟蓝牙传输温湿度

#include <stdio.h>

typedef struct _ble_t 
{
    // 省略和底层相关的属性
} ble_t;

// 静态构造函数
void ble_init(ble_t* ble) 
{
    // 省略对底层相关属性初始化
}

// 模拟蓝牙发送
void ble_send(ble_t* ble, int temp, int hum) 
{
    printf("send temp:%d\n", temp);
    printf("send hum:%d\n", hum);
}

再创建一个温湿度传感器类,模拟获取温湿度

typedef struct _sensor_t
{
    int temp;
    int hum;
    // 省略和底层相关的属性
    display_t *disp;
    ble_t *ble;
} sensor_t;

// 静态构造函数
void sensor_init(sensor_t* sensor, display_t *disp, ble_t *ble)
{
    sensor->temp = 0;
    sensor->hum = 0;
    sensor->disp = disp;
    sensor->ble = ble;
}

// 模拟温湿度传感器读取数值
void sensor_get(sensor_t *sensor, int temp, int hum) 
{
    sensor->temp = temp;
    sensor->hum = hum;
    display_show(sensor->disp, temp, hum);
    ble_send(sensor->ble, temp, hum);
}

写一个调用的 main。

int main() 
{
    // 初始化显示模块
    display_t my_display;
    display_init(&my_display);
    
    // 初始化蓝牙模块
    ble_t my_ble;
    ble_init(&my_ble);
    
    // 初始化温湿度传感器
    sensor_t sensor;
    sensor_init(&sensor, &my_display, &my_ble);
    
    // 模拟读取温湿度数据(25°C,50%湿度)
    sensor_get(&sensor, 25, 50);
    
    return 0;
}

运行结果:

temp:25
hum:50
send temp:25
send hum:50

虽然我们用最简单的方式完成了需求。但是,上述做法有个最大的问题。就是温湿度传感器对象内部需要依赖 display 和 ble 对象。这导致两个耦合问题:

  • 当 display 和 ble 对象属性或者方法修改时,对应的温湿度传感器对象也要修改。
  • 当有新的显示媒介对象接入后,温湿度传感器要修改。

Image

总之,这种方式会导致对象之间的相互依赖,数据很难解耦。

基于观察者模式的思路

首先,还是原封不动封装  display  类和   ble 类。这两种类,都被称之为“观察者”。我们为“观察者们”建立一个统一的父类 Observer:

typedef struct _observer_t
{ 
    void (*update)(struct _observer_t*, int, int);
} observer_t ;

void observer_init(observer_t *obs)
{
    obs->update = NULL;
}

void observer_update(observer_t *obs, int temp, int hum)
{
    if (obs->update != NULL)
    {
        obs->update(obs, temp, hum);
    }
}

这个接口将所有的观察者在观测时发生的不同动作进行统一包装。

接下来,我们让 display 类继承它:


#include <stdio.h>

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) 
{
    observer_init(&display->obs);
    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);
}

这样,当数据发生变化的时候。我们无需关注子类如何实现,直接关注它的父类的 update 方法即可。
我们同样让 ble 类继承 observer 类:

#include <stdio.h>

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) 
{
    observer_init(&ble->obs);
    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);
}

接下来,我们来看温湿度传感器类。该类属于被观察的对象,需要完成 attach,detach 和 notify。其中,attach 和 detach 是用于收集观察者。而 notify 则是遍历收集到的观察者,并统一调用 update。

typedef struct _sensor_t 
{
    int temp;
    int hum;
    observer_t *obs;
} sensor_t;

// 静态构造函数
void sensor_init(sensor_t *sensor)
{
    sensor->temp = 0;
    sensor->hum = 0;
    sensor->obs = NULL;
}

// 添加观察者
void sensor_attach(sensor_t *sensor, observer_t *obs)
{
    if (!sensor->obs) {
        sensor->obs = obs;
    } else {
        observer_t *cur = sensor->obs;
        while (cur->next) cur = cur->next;
        cur->next = obs;
    }
}

// 删除观察者
void sensor_detach(sensor_t *sensor, observer_t *obs)
{
    observer_t **indirect = &sensor->obs; // 使用二级指针简化操作
    while (*indirect) {
        if (*indirect == obs) {
            *indirect = obs->next; // 绕过目标节点
            obs->next = NULL;      // 重置观察者的next指针
            return;
        }
        indirect = &(*indirect)->next;
    }
}

// 通知观察者
void sensor_notify(sensor_t *sensor, int temp, int hum)
{
    observer_t *_obs = sensor->obs;
    while(_obs)
    {
        observer_update(_obs, temp, hum);
        _obs = _obs->next;
    }
}

// 模拟温湿度传感器读取数值
void sensor_get(sensor_t *sensor, int temp, int hum) 
{
    sensor->temp = temp;
    sensor->hum = hum;
    sensor_notify(sensor, temp, hum);
}

根据需求,我们要简单修改一下观察者类:

typedef struct _observer_t
{ 
    void (*update)(struct _observer_t*, int, int);
    struct _observer_t *next;
} observer_t ;

void observer_init(observer_t *obs)
{
    obs->update = NULL;
    obs->next= NULL;
}

void observer_update(observer_t *obs, int temp, int hum)
{
    if (obs->update != NULL)
    {
        obs->update(obs, temp, hum);
    }
}

最后,让我们写一个例子看看:

int main() 
{
    // 创建传感器和观察者实例
    sensor_t sensor;
    sensor_init(&sensor);
    display_t display;
    display_init(&display);
    ble_t ble;
    ble_init(&ble);


    // 注册观察者(显示和蓝牙)
    sensor_attach(&sensor, (observer_t*)&display);
    sensor_attach(&sensor, (observer_t*)&ble);

    // 模拟第一次数据读取(会触发两个观察者)
    printf("=== First measurement ===\n");
    sensor_get(&sensor, 25, 60);

    // 移除蓝牙观察者
    sensor_detach(&sensor, (observer_t*)&ble);

    // 模拟第二次数据读取(只会触发显示)
    printf("\n=== Second measurement ===\n");
    sensor_get(&sensor, 26, 58);

    return 0;
}

运行结果:

== First measurement ===
temp:25
hum:60
send temp:25
send hum:60

=== Second measurement ===
temp:26
hum:58

如果上述“干巴巴”的代码不好理解,我总结了下面这幅 UML 图帮助理解:

Image

通过观察者这个父类。传感器类的依赖变成了稳定的观察者类而不是子类。如果修改子类的方法,与传感器类无关。如果增加一个新的传感器,只需要继承观察者类,并重写 update 即可。对其它类无任何影响。

多主题多观察者模型

在上述的模型中,我们只考虑了传感器是一种。那么当传感器有多种的时候,虽然不同传感器对象不同。但是共同都有 notify,detach 和 attach 三种方法。这种情况我们就想到了,能否将这部分的内容封装成父类 subject:

typedef struct _subject_t
{
    observer_t *obs;
} subject_t;

// 静态初始化主题对象
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;
    }
}

而温湿度传感器对象则继承 subject:

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((subject_t *)sensor, temp, hum);
}

接下来我们来优化其调用,之前的调用是需要手动调用 attach 和 detach 方法。我们可以通过让观察者初始化时指定主题对象,来达到减少调用的目的。

typedef struct _observer_t
{ 
    void (*update)(struct _observer_t*, int, int);
    struct _observer_t *next;
    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_t *subject)
{
    subject_detach(obs->subject, obs);
}

void observer_update(observer_t *obs, int temp, int hum)
{
    if (obs->update != NULL)
    {
        obs->update(obs, temp, hum);
    }
}

对应的 display 和 ble 的构造函数也得修改

#include <stdio.h>

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->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);
}

最后,我们添加 main。对上述代码进行调用:

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;
}

运行结果:

=== First Update ===
temp:25
hum:60
send temp:25
send hum:60

=== Detaching BLE ===

=== Second Update ===
temp:26
hum:58

这种方式, 如果有多个对象继承 subject,然后在对应的函数里调用 notify 就行了。此外,这次修改可以在观察者初始化的时候订阅主题对象。这样就不必麻烦的调用 attach 和 detach 方法了,让调用变得更加简单。

Image

发布订阅模式和观察者模式的区别

发布订阅模式和消息订阅模式是一样的吗?

消息订阅模式,是一种各个系统组件的通信方式。在这种模式下,发布者可以是系统中不同类型的消息服务或者事件服务。而订阅者可以是具体的网关服务,路由服务等。发布者和订阅者互相都不知道对方的存在,通过消息代理部分进行分发。订阅者会告诉消息代理自己感兴趣的消息类型,当发布者进行发布对应的消息时,订阅者就会将该消息转发给订阅者进行处理。这就是发布订阅的基本形式。

image.png

以下提供完整可运行代码,供学习理解

最简单直接的思路——完整代码

#include <stdio.h>

typedef struct _display_t 
{
    // 省略和底层相关的属性
} display_t;

// 静态构造函数
void display_init(display_t *display) 
{
    // 省略对底层相关属性初始化
}

// 模拟屏幕显示
void display_show(display_t *display, int temp, int hum) 
{
    printf("temp:%d\n", temp);
    printf("hum:%d\n", hum);
}

typedef struct _ble_t 
{
    // 省略和底层相关的属性
} ble_t;

// 静态构造函数
void ble_init(ble_t* ble) 
{
    // 省略对底层相关属性初始化
}

// 模拟蓝牙发送
void ble_send(ble_t* ble, int temp, int hum) 
{
    printf("send temp:%d\n", temp);
    printf("send hum:%d\n", hum);
}

typedef struct _sensor_t
{
    int temp;
    int hum;
    // 省略和底层相关的属性
    display_t *disp;
    ble_t *ble;
} sensor_t;

// 静态构造函数
void sensor_init(sensor_t* sensor, display_t *disp, ble_t *ble)
{
    sensor->temp = 0;
    sensor->hum = 0;
    sensor->disp = disp;
    sensor->ble = ble;
}

// 模拟温湿度传感器读取数值
void sensor_get(sensor_t *sensor, int temp, int hum) 
{
    sensor->temp = temp;
    sensor->hum = hum;
    display_show(sensor->disp, temp, hum);
    ble_send(sensor->ble, temp, hum);
}


int main() 
{
    // 初始化显示模块
    display_t my_display;
    display_init(&my_display);
    
    // 初始化蓝牙模块
    ble_t my_ble;
    ble_init(&my_ble);
    
    // 初始化温湿度传感器
    sensor_t sensor;
    sensor_init(&sensor, &my_display, &my_ble);
    
    // 模拟读取温湿度数据(25°C,50%湿度)
    sensor_get(&sensor, 25, 50);
    
    return 0;
}

基于观察者模式的思路——完整代码

#include <stdio.h>

typedef struct _observer_t
{
    void (*update)(struct _observer_t*, int, int);
    struct _observer_t *next;
} observer_t;

void observer_init(observer_t *obs)
{
    obs->update = NULL;
    obs->next= NULL;
}

void observer_update(observer_t *obs, int temp, int hum)
{
    if (obs->update != NULL)
    {
        obs->update(obs, temp, hum);
    }
}

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) 
{
    observer_init(&display->obs);
    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) 
{
    observer_init(&ble->obs);
    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 
{
    int temp;
    int hum;
    observer_t *obs;
} sensor_t;

// 静态构造函数
void sensor_init(sensor_t *sensor)
{
    sensor->temp = 0;
    sensor->hum = 0;
    sensor->obs = NULL;
}

// 添加观察者
void sensor_attach(sensor_t *sensor, observer_t *obs)
{
    if (!sensor->obs) {
        sensor->obs = obs;
    } else {
        observer_t *cur = sensor->obs;
        while (cur->next) cur = cur->next;
        cur->next = obs;
    }
}

// 删除观察者
void sensor_detach(sensor_t *sensor, observer_t *obs)
{
    observer_t **indirect = &sensor->obs; // 使用二级指针简化操作
    while (*indirect) {
        if (*indirect == obs) {
            *indirect = obs->next; // 绕过目标节点
            obs->next = NULL;      // 重置观察者的next指针
            return;
        }
        indirect = &(*indirect)->next;
    }
}

// 通知观察者
void sensor_notify(sensor_t *sensor, int temp, int hum)
{
    observer_t *_obs = sensor->obs;
    while(_obs)
    {
        _obs->update(_obs, temp, hum);
        _obs = _obs->next;
    }
}

// 模拟温湿度传感器读取数值
void sensor_get(sensor_t *sensor, int temp, int hum) 
{
    sensor->temp = temp;
    sensor->hum = hum;
    sensor_notify(sensor, temp, hum);
}

int main() {
    // 创建传感器和观察者实例
    sensor_t sensor;
    sensor_init(&sensor);
    display_t screen;
    display_init(&screen);
    ble_t ble_device;
    ble_init(&ble_device);


    // 注册观察者(显示和蓝牙)
    sensor_attach(&sensor, (observer_t*)&screen);
    sensor_attach(&sensor, (observer_t*)&ble_device);

    // 模拟第一次数据读取(会触发两个观察者)
    printf("=== First measurement ===\n");
    sensor_get(&sensor, 25, 60);

    // 移除蓝牙观察者
    sensor_detach(&sensor, (observer_t*)&ble_device);

    // 模拟第二次数据读取(只会触发显示)
    printf("\n=== Second measurement ===\n");
    sensor_get(&sensor, 26, 58);

    return 0;
}

多主题多观察者模型——完整代码

#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;
}

END

原文:裸机思维

专栏推荐文章

如果你喜欢我的思维,欢迎订阅裸机思维欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
1491
内容数
125
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息