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