vesperW · 3月19日

嵌入式编程模型 | 观察者模式

大家好,我是杂烩君。

本次我们分享的是嵌入式中常用的一种思想 / 编程模型——观察者模式。

观察者模式概述

观察者模式(Observer Pattern)是一种行为设计模式,其核心在于建立对象间的动态订阅-通知机制。

它定义了对象之间的一对多依赖关系,当一个对象(被观察对象,也称为主题)的状态发生变化时,所有依赖它的对象(观察者)都会收到通知并自动更新。

在嵌入式系统中,观察者模式广泛应用于解耦事件发布者与订阅者,特别适合的应用场景:

  • 处理传感器数据更新
  • 硬件状态变化
  • 多模块协作

嵌入式应用场景

1.传感器数据分发

多个模块(如显示、存储、报警)需要实时获取传感器数据变化,观察者模式可将传感器作为主题(Subject),各模块作为观察者(Observer),实现数据更新时的自动通知。

类图:

image.png

主题类(SensorSubject)

  • 包含观察者列表(observers 数组)
  • 维护当前传感器值(sensor_value)
  • 提供 attach 和 set_value 两个关键方法

观察者接口(ObserverCallback)

  • 定义统一的 update 接口
  • 对应代码中的函数指针类型

具体观察者类

  • DisplayObserver:处理显示更新
  • LoggerObserver:处理日志记录
  • AlarmObserver:处理阈值报警
代码:
#include<stdio.h>

#define OBSERVER_MAX_NUM  5

// 观察者回调函数类型
typedefvoid(*ObserverCallback)(int value);

// 主题(被观察者)
typedefstruct
{
    ObserverCallback observers[OBSERVER_MAX_NUM];
    int count;
    int sensor_value;
} SensorSubject;

// 附加观察者到主题
voidsensor_attach(SensorSubject* subject, ObserverCallback callback)
{
    if (!subject || !callback) 
    {
        printf("Invalid parameters!\n");
        return;
    }

    if (subject->count >= OBSERVER_MAX_NUM) 
    {
        printf("Observers full!\n");
        return;
    }

    subject->observers[subject->count++] = callback;
}

// 更新传感器值并通知观察者
voidsensor_set_value(SensorSubject* subject, int value)
{
    if (!subject) 
    {
        printf("Invalid parameters!\n");
        return;
    }

    subject->sensor_value = value;
    
    // 遍历所有观察者进行通知
    for (int i = 0; i < subject->count; ++i) 
    {
        if (subject->observers[i]) 
        {
            subject->observers[i](subject->sensor_value);
        }
    }
}

// 观察者1:显示模块
voiddisplay_update(int value)
{
    printf("[Display] Value: %d\n", value);
}

// 观察者2:日志模块回
voidlogger_update(int value)
{
    printf("[Logger] Value: %d\n", value);
}

// 观察者3:报警模块
voidalarm_update(int value)
{
    if (value > 100) 
    {
        printf("[Alarm] Value %d exceeds limit!\n", value);
    }
}

intmain(void)
{
    // 初始化传感器主题
    SensorSubject sensor = 
    {
        .observers = {0},
        .count = 0,
        .sensor_value = 0
    };

    // 注册观察者
    sensor_attach(&sensor, display_update);
    sensor_attach(&sensor, logger_update);
    sensor_attach(&sensor, alarm_update);

    // 模拟传感器数据更新
    sensor_set_value(&sensor, 25);
    sensor_set_value(&sensor, 120);

    return0;
}

image.png

这个例子允许对象(显示、日志、报警模块)订阅另一个对象(传感器),当主题状态变化时自动通知所有观察者。

注意:这个例子只是为了解释观察者模式的基本思想,在单线程环境下基本实现了观察者模式的核心功能。若需要模仿应用于实际应用,需要增加线程安全机制、动态内存管理、更完善的错误处理等。

2.Zephyr 传感器子系统

在 Zephyr 中,传感器子系统使用了类似观察者模式的机制。传感器驱动作为主题,当传感器数据更新时,会触发相应的事件。

而应用程序可以注册为观察者,监听这些事件并在数据更新时进行处理。

#include<zephyr.h>
#include<device.h>
#include<devicetree.h>
#include<drivers/sensor.h>

// 传感器事件处理函数,作为观察者的更新方法
staticvoidsensor_callback(const struct device *dev, struct sensor_trigger *trig)
{
    structsensor_valuetemp;

    if (sensor_sample_fetch(dev) < 0) {
        return;
    }
    if (sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp) < 0) {
        return;
    }
    // 处理传感器数据
    printk("Temperature: %d.%06d\n", temp.val1, temp.val2);
}

voidmain(void)
{
    conststructdevice *dev = device_get_binding(DT_LABEL(DT_INST(0, st_stts751)));
    if (dev == NULL) {
        return;
    }

    structsensor_triggertrig = {
       .type = SENSOR_TRIG_DATA_READY,
       .chan = SENSOR_CHAN_AMBIENT_TEMP
    };

    // 注册传感器事件回调,相当于注册观察者
    if (sensor_trigger_set(dev, &trig, sensor_callback) < 0) {
        return;
    }

    while (1) {
        k_sleep(K_MSEC(100));
    }
}

往期相关文章:Zephyr 会成为物联网时代 RTOS 的佼佼者?

3.任务间通信和同步机制

在 RTOS 中,任务之间的通信和同步机制可以类比为观察者模式。

EventGroupHandle_t xEventGroup;

// 创建事件组
xEventGroup = xEventGroupCreate();

// 任务 1 作为主题设置事件
voidvTask1( void *pvParameters )
{
    while(1)
    {
        // 设置事件位
        xEventGroupSetBits( xEventGroup, 0x01 );
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}

// 任务 2 作为观察者等待事件
voidvTask2( void *pvParameters )
{
    EventBits_t uxBits;
    while(1)
    {
        // 等待事件位
        uxBits = xEventGroupWaitBits(
            xEventGroup,   // 事件组句柄
            0x01,          // 等待的事件位
            pdTRUE,        // 退出时清除事件位
            pdFALSE,       // 不需要所有位都设置
            portMAX_DELAY  // 无限期等待
        );
        // 处理事件
        if( ( uxBits & 0x01 ) != 0 )
        {
            // 执行相应操作
        }
    }
}

事件组(Event Group)就可以看作是一个主题,而等待这些事件的任务则可以看作是观察者。

当事件组中的某个事件被设置(状态改变)时,等待该事件的任务会被唤醒并执行相应的操作,就如同观察者接收到主题的通知后进行更新一样。

往期相关文章:嵌入式事件标志组

4.MQTT

MQTT 是一种轻量级的消息传输协议,主要用于物联网设备之间的通信,其在设计和使用上应用了观察者模式的思想。

其核心概念包括:

  • 发布者(Publisher):产生消息并将其发布到特定的主题(Topic)。
  • 主题(Topic):消息的分类标签,用于区分不同类型的消息。
  • 代理(Broker):负责接收发布者的消息,并将消息转发给订阅了相应主题的订阅者。
  • 订阅者(Subscriber):订阅一个或多个主题,当这些主题有新消息发布时,会收到代理转发的消息。

END

来源:嵌入式应用研究院

推荐阅读

欢迎大家点赞留言,更多 Arm 技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
2914
内容数
341
分享一些在嵌入式应用开发方面的浅见,广交朋友
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息