C语言从句柄到对象 (八) —— 当对象会说话:观察者模式与事件链表

前言: 在之前的文章中,所有的调用方向都是 App -> Driver (比如 SetSpeed)。 但在实际业务中,我们经常遇到反向需求:Driver -> App。

比如:当电机发生"过流堵转"故障时,驱动层需要通知上层。

  • UI 模块 需要弹窗报警。

  • Log 模块 需要记录日志。

  • 控制模块 需要立即停机。

如果我们只用普通的"回调函数" SetCallback(func),由于函数指针变量只有一个,你只能注册一个接收者。如果你注册了 UI,Log 就收不到了。

今天我们用 C 语言实现 观察者模式 (Observer Pattern) ,实现 一对多 的事件通知。


一、 定义观察者链表

我们需要一种机制,允许任意数量的模块"订阅"电机的事件。 最简单的方法是使用 单向链表。

1.1 定义节点

我们在头文件中定义一个"观察者节点"。

// motor_driver.h

// 事件回调函数的原型

typedef void (*Motor_EventHandler)(Motor_Handle h, int event, void *ctx);

// 观察者节点

typedef struct Motor_Observer_Node {

Motor_EventHandler callback; // 谁来处理?

void *context; // 上下文 (传给回调的参数)

struct Motor_Observer_Node *next; // 指向下一个观察者

} Motor_Observer_t;

1.2 在对象中加入链表头

// motor_driver.c (内部结构体)

struct Motor_t {

// ... 其他属性 ...

// 【核心】观察者链表的头指针

Motor_Observer_t *observer_list;

};

二、 注册与注销

我们需要提供 API,让外部模块把自己挂到链表上。

// motor_driver.c

// 注册观察者 (订阅)

// 注意:node 内存通常由调用者提供 (可以是静态变量),避免驱动内部 malloc

void Motor_RegisterObserver(Motor_Handle h, Motor_Observer_t *node) {

struct Motor_t *p = &motor_poolh;

// 头插法:把新节点直接插到链表最前面 (最简单)

OS_ENTER_CRITICAL(); // 链表操作要注意线程安全

node->next = p->observer_list;

p->observer_list = node;

OS_EXIT_CRITICAL();

}

三、 发布事件 (Publish)

当电机驱动检测到故障时,它不需要知道谁关心这个故障,它只需要 "大吼一声"(遍历链表)。

// motor_driver.c (内部函数)

static void Notify_Observers(struct Motor_t *p, int event) {

// 从头开始遍历

Motor_Observer_t *curr = p->observer_list;

while (curr != NULL) {

// 调用回调函数

if (curr->callback) {

curr->callback(Get_Handle(p), event, curr->context);

}

// 找下一个

curr = curr->next;

}

}

// 中断服务函数或周期任务

void Motor_IRQ_Handler(void) {

// ... 检测到堵转 ...

Notify_Observers(current_motor, EVENT_OVER_CURRENT);

}

四、 业务层怎么用?

现在,UI 模块和 Log 模块可以同时监听电机了。

// ui_module.c

static Motor_Observer_t ui_node; // 静态分配节点

void UI_OnMotorEvent(Motor_Handle h, int evt, void *ctx) {

if (evt == EVENT_OVER_CURRENT) ShowAlert("Motor Blocked!");

}

void UI_Init() {

ui_node.callback = UI_OnMotorEvent;

Motor_RegisterObserver(hMotor, &ui_node);

}

// log_module.c

static Motor_Observer_t log_node;

void Log_OnMotorEvent(Motor_Handle h, int evt, void *ctx) {

Log_Write("Motor Error: %d", evt);

}

void Log_Init() {

log_node.callback = Log_OnMotorEvent;

Motor_RegisterObserver(hMotor, &log_node); // 这里的注册互不影响

}

五、 全剧终:C语言架构的尽头

至此,我们的 《 C语言从句柄到对象》 专栏就真正做到了"滴水不漏"。

  1. 静态结构:句柄、封装、多态、继承。

  2. 动态行为:并发保护、事件分发。

这套方法论,基本上就是 Linux Kernel 和 大型嵌入式框架 (如 Zephyr/RT-Thread) 的设计缩影。掌握了这 8 篇文章,你眼中的 C 语言将不再是零散的语法,而是一门用来构建精密系统的艺术语言。

恭喜你,架构师之路,正式启程。

/*******************************************
* Description:
* 本文为作者《嵌入式开发基础与工程实践》系列文之一。
* 关注我即可订阅后续内容更新,采用异步推送机制。
* 转发本文可视为广播分发,有助于信息传播至更多节点。
*******************************************/

相关推荐
xiaoye-duck1 小时前
《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试
开发语言·网络·c++·语言模型
nice_lcj5204 小时前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试
码农小河664 小时前
基于SSM+JSP+MySQL的在线医疗服务系统的设计与实现
java·开发语言·mysql
Dev_XH4 小时前
【成品设计】基于单片机的家用智能浇花系统的设计与实现
stm32·单片机·嵌入式硬件·物联网
老师我太想进步了20265 小时前
【数据结构-4】算法美学之顺序表
数据结构·算法
意法半导体STM326 小时前
【官方原创】LAT1717 STM32N6 如何使用单个XSPI驱动双片相同HyperRAM
stm32·单片机·嵌入式硬件·mcu·stm32n6·hyperram
木子单片机6 小时前
基于单片机的直流电机调速系统设计
单片机·嵌入式硬件·51单片机·nosql·keil
Zwawa7 小时前
两根线怎么挂住一堆设备:I2C 协议原理
c语言·单片机·嵌入式
nice_lcj5208 小时前
ThreadLocal 入门:它是怎么隔离数据的、为什么会导致内存泄漏
java·开发语言·后端·面试·并发编程·内存泄漏·threadlocal
紧跟先前的步伐8 小时前
Go AI Agent 实战 28|Agent 的预算、超时与停止条件
开发语言·人工智能·后端·ai·golang