大家好,我是雜燴君。本次我們分享的是嵌入式中常用的一種思想 / 編程模型——觀察者模式。
觀察者模式概述
觀察者模式(Observer Pattern)是一種行為設(shè)計(jì)模式,其核心在于建立對象間的動(dòng)態(tài)訂閱-通知機(jī)制。
它定義了對象之間的一對多依賴關(guān)系,當(dāng)一個(gè)對象(被觀察對象,也稱為主題)的狀態(tài)發(fā)生變化時(shí),所有依賴它的對象(觀察者)都會(huì)收到通知并自動(dòng)更新。
在嵌入式系統(tǒng)中,觀察者模式廣泛應(yīng)用于解耦事件發(fā)布者與訂閱者,特別適合的應(yīng)用場景:
嵌入式應(yīng)用場景
1、傳感器數(shù)據(jù)分發(fā)
多個(gè)模塊(如顯示、存儲(chǔ)、報(bào)警)需要實(shí)時(shí)獲取傳感器數(shù)據(jù)變化,觀察者模式可將傳感器作為主題(Subject),各模塊作為觀察者(Observer),實(shí)現(xiàn)數(shù)據(jù)更新時(shí)的自動(dòng)通知。
類圖:
主題類(SensorSubject):
包含觀察者列表(observers數(shù)組)
維護(hù)當(dāng)前傳感器值(sensor_value)
提供attach和set_value兩個(gè)關(guān)鍵方法
觀察者接口(ObserverCallback):
- 定義統(tǒng)一的update接口對應(yīng)代碼中的函數(shù)指針類型
具體觀察者類:
- DisplayObserver:處理顯示更新LoggerObserver:處理日志記錄AlarmObserver:處理閾值報(bào)警
代碼:
#include<stdio.h>
#define?OBSERVER_MAX_NUM ?5
// 觀察者回調(diào)函數(shù)類型
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;
? ??
? ??// 遍歷所有觀察者進(jìn)行通知
? ??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: %dn", value);
}
// 觀察者2:日志模塊回
voidlogger_update(int?value)
{
? ??printf("[Logger] Value: %dn", value);
}
// 觀察者3:報(bào)警模塊
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);
? ??// 模擬傳感器數(shù)據(jù)更新
? ? sensor_set_value(&sensor,?25);
? ? sensor_set_value(&sensor,?120);
? ??return0;
}
這個(gè)例子允許對象(顯示、日志、報(bào)警模塊)訂閱另一個(gè)對象(傳感器),當(dāng)主題狀態(tài)變化時(shí)自動(dòng)通知所有觀察者。
注意:這個(gè)例子只是為了解釋觀察者模式的基本思想,在單線程環(huán)境下基本實(shí)現(xiàn)了觀察者模式的核心功能。若需要模仿應(yīng)用于實(shí)際應(yīng)用,需要增加線程安全機(jī)制、動(dòng)態(tài)內(nèi)存管理、更完善的錯(cuò)誤處理等。
2、Zephyr傳感器子系統(tǒng)
在 Zephyr 中,傳感器子系統(tǒng)使用了類似觀察者模式的機(jī)制。傳感器驅(qū)動(dòng)作為主題,當(dāng)傳感器數(shù)據(jù)更新時(shí),會(huì)觸發(fā)相應(yīng)的事件。
而應(yīng)用程序可以注冊為觀察者,監(jiān)聽這些事件并在數(shù)據(jù)更新時(shí)進(jìn)行處理。
#include<zephyr.h>
#include<device.h>
#include<devicetree.h>
#include<drivers/sensor.h>
// 傳感器事件處理函數(shù),作為觀察者的更新方法
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;
? ? }
? ??// 處理傳感器數(shù)據(jù)
? ? printk("Temperature: %d.%06dn", 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
? ? };
? ??// 注冊傳感器事件回調(diào),相當(dāng)于注冊觀察者
? ??if?(sensor_trigger_set(dev, &trig, sensor_callback) <?0) {
? ? ? ??return;
? ? }
? ??while?(1) {
? ? ? ? k_sleep(K_MSEC(100));
? ? }
}
往期相關(guān)文章:Zephyr 會(huì)成為物聯(lián)網(wǎng)時(shí)代RTOS的佼佼者?
3、任務(wù)間通信和同步機(jī)制
在 RTOS 中,任務(wù)之間的通信和同步機(jī)制可以類比為觀察者模式。
EventGroupHandle_t xEventGroup;
// 創(chuàng)建事件組
xEventGroup = xEventGroupCreate();
// 任務(wù) 1 作為主題設(shè)置事件
voidvTask1(?void?*pvParameters )
{
? ??while(1)
? ? {
? ? ? ??// 設(shè)置事件位
? ? ? ? xEventGroupSetBits( xEventGroup,?0x01?);
? ? ? ? vTaskDelay( pdMS_TO_TICKS(?1000?) );
? ? }
}
// 任務(wù) 2 作為觀察者等待事件
voidvTask2(?void?*pvParameters )
{
? ? EventBits_t uxBits;
? ??while(1)
? ? {
? ? ? ??// 等待事件位
? ? ? ? uxBits = xEventGroupWaitBits(
? ? ? ? ? ? xEventGroup, ??// 事件組句柄
? ? ? ? ? ??0x01, ? ? ? ? ?// 等待的事件位
? ? ? ? ? ? pdTRUE, ? ? ? ?// 退出時(shí)清除事件位
? ? ? ? ? ? pdFALSE, ? ? ??// 不需要所有位都設(shè)置
? ? ? ? ? ? portMAX_DELAY ?// 無限期等待
? ? ? ? );
? ? ? ??// 處理事件
? ? ? ??if( ( uxBits &?0x01?) !=?0?)
? ? ? ? {
? ? ? ? ? ??// 執(zhí)行相應(yīng)操作
? ? ? ? }
? ? }
}
事件組(Event Group)就可以看作是一個(gè)主題,而等待這些事件的任務(wù)則可以看作是觀察者。
當(dāng)事件組中的某個(gè)事件被設(shè)置(狀態(tài)改變)時(shí),等待該事件的任務(wù)會(huì)被喚醒并執(zhí)行相應(yīng)的操作,就如同觀察者接收到主題的通知后進(jìn)行更新一樣。
往期相關(guān)文章:嵌入式事件標(biāo)志組
4、MQTT
MQTT 是一種輕量級的消息傳輸協(xié)議,主要用于物聯(lián)網(wǎng)設(shè)備之間的通信,其在設(shè)計(jì)和使用上應(yīng)用了觀察者模式的思想。
其核心概念包括:
發(fā)布者(Publisher):產(chǎn)生消息并將其發(fā)布到特定的主題(Topic)。
主題(Topic):消息的分類標(biāo)簽,用于區(qū)分不同類型的消息。
代理(Broker):負(fù)責(zé)接收發(fā)布者的消息,并將消息轉(zhuǎn)發(fā)給訂閱了相應(yīng)主題的訂閱者。
訂閱者(Subscriber):訂閱一個(gè)或多個(gè)主題,當(dāng)這些主題有新消息發(fā)布時(shí),會(huì)收到代理轉(zhuǎn)發(fā)的消息。
以上就是本次的分享,如果覺得文章有幫助,麻煩幫忙轉(zhuǎn)發(fā),謝謝!