嵌入式开发工程必备的六大设计模式
设计模式是软件开发中前人总结的经典“套路”用于解决特定场景下的代码设计问题。在嵌入式开发中合理使用设计模式能极大提升代码的可维护性、可扩展性和可移植性。但要特别注意嵌入式资源有限ROM/RAM不能生搬硬套比如避免滥用malloc动态内存。下面我结合C语言GD32常用 和嵌入式实战场景为你详解这6种模式。1. 单例模式 (Singleton)**目的**保证一个类结构体在系统中只有一个实例并提供一个全局访问点。**嵌入式场景**管理唯一的硬件资源如系统日志模块、唯一的外设如RTC 或全局配置管理器。C语言实现核心技巧静态局部变量// sys_config.htypedefstruct{intbaud_rate;intsys_tick;}SysConfig_t;// sys_config.cSysConfig_t*GetSysConfig(void){// 静态局部变量存放在静态存储区只会被初始化一次staticSysConfig_t instance{.baud_rate115200,.sys_tick0};returninstance;// 返回统一指针}// 使用任何地方调用 GetSysConfig() 拿到的都是同一个地址GetSysConfig()-baud_rate9600;注意事项在裸机无RTOS下安全在RTOS多任务下若涉及写操作需加互斥锁保护。2. 工厂模式 (Factory)**目的**定义一个用于创建对象结构体的接口让子类/具体模块决定实例化哪一个类。将“创建”和“使用”分离。**嵌入式场景**系统挂载了多个不同型号的传感器如温度传感器有DS18B20、SHT30应用层不需要知道具体型号只需向“工厂”要一个“温度传感器对象”。C语言实现核心技巧函数指针 条件分支// 抽象传感器接口typedefstruct{int(*init)(void);float(*read)(void);}SensorOps_t;// 工厂根据类型号返回具体的操作函数集SensorOps_t*SensorFactory_Create(inttype){staticSensorOps_t ds18b20_ops{DS18B20_Init,DS18B20_Read};staticSensorOps_t sht30_ops{SHT30_Init,SHT30_Read};switch(type){caseTYPE_DS18B20:returnds18b20_ops;caseTYPE_SHT30:returnsht30_ops;default:returnNULL;}}// 应用层使用SensorOps_t*temp_sensorSensorFactory_Create(TYPE_SHT30);temp_sensor-init();floattemptemp_sensor-read();优势新增传感器时只需修改工厂函数上层应用代码init/read调用完全不用改。3. 观察者模式 (Observer)**目的**定义对象间的一对多依赖关系。当一个对象主题状态改变时所有依赖它的对象观察者都会自动收到通知并更新。**嵌入式场景**按键中断触发需要同时通知“LED闪烁模块”、“蜂鸣器模块”和“界面显示模块”。C语言实现核心技巧回调函数链表#defineMAX_OBSERVERS5typedefvoid(*NotifyCallback)(intevent);typedefstruct{NotifyCallback callbacks[MAX_OBSERVERS];intcount;}Subject_t;voidSubject_Attach(Subject_t*sub,NotifyCallback cb){if(sub-countMAX_OBSERVERS)sub-callbacks[sub-count]cb;}voidSubject_Notify(Subject_t*sub,intevent){for(inti0;isub-count;i){sub-callbacks[i](event);// 逐个通知观察者}}// 使用示例Subject_t key_subject;Subject_Attach(key_subject,LED_Blink);Subject_Attach(key_subject,Buzzer_Beep);// 按键中断触发时Subject_Notify(key_subject,KEY_PRESSED);注意观察者模式通常是同步调用的在通知者线程中执行执行时长不能阻塞。4. 状态模式 (State)**目的**允许对象在内部状态改变时改变其行为。将复杂的 if-else / switch-case 状态机解耦把每个状态的行为封装到独立的“状态对象”中。**嵌入式场景**复杂的通信协议握手如TCP状态、菜单导航界面、电源管理运行/休眠/故障。C语言实现核心技巧函数指针表// 定义状态操作接口typedefstructStateMachineStateMachine_t;typedefstruct{void(*handle)(StateMachine_t*machine,intevent);}State_t;// 定义状态机上下文structStateMachine{constState_t*current_state;intdata;};// 具体状态A的handle函数staticvoidStateA_Handle(StateMachine_t*machine,intevent){if(eventEVT_NEXT){printf(State A - State B);machine-current_statestate_b;// 切换状态}}// 使用machine.current_statestate_a;machine.current_state-handle(machine,EVT_NEXT);优势新增“State C”时无需修改旧状态逻辑符合“开闭原则”。5. 发布-订阅模式 (Publish-Subscribe)**目的**这是观察者模式的“进阶解耦版”。发布者Publisher不直接通知订阅者Subscriber而是通过一个中间件消息总线/事件队列 转发。发布者和订阅者完全不知道对方的存在。**嵌入式场景**RTOS环境下的跨任务通信如FreeRTOS的消息队列、GUI框架的事件分发。区别对比重点1观察者模式同步主题持有观察者指针直接调用回调两者耦合在一起。2发布-订阅异步通过中间层队列传递。发布者只管发消息订阅者只管收消息。C语言实现基于RTOS队列// 发布者任务voidTask_Sensor(void*pv){inttempReadTemp();xQueueSend(g_event_queue,temp,0);// 只管发不管谁收}// 订阅者任务1界面voidTask_Display(void*pv){inttemp;while(1){if(xQueueReceive(g_event_queue,temp,portMAX_DELAY)){UpdateLCD(temp);}}}// 订阅者任务2存储voidTask_Storage(void*pv){inttemp;while(1){if(xQueueReceive(g_event_queue,temp,portMAX_DELAY)){SaveToSD(temp);}}}6. 适配器模式 (Adapter)**目的**将一个类的接口转换成客户希望的另一个接口。解决接口不兼容的问题让原本不能一起工作的类可以一起工作。**嵌入式场景**硬件抽象层HAL 的终极体现。例如你的代码原先是基于ST库的 HAL_GPIO_WritePin现在要移植到GD32库的 gpio_bit_write不需要改应用层只需写一个适配器。C语言实现核心技巧中间层封装// 目标接口应用层期望的typedefstruct{void(*write_pin)(uint8_tlevel);}GpioAdapter_t;// 适配器实现将GD32特有的API适配成目标接口staticvoidGD32_WritePin_Impl(uint8_tlevel){// 内部调用GD32原厂库函数gpio_bit_write(GPIOA,GPIO_PIN_8,(bit_status)level);}GpioAdapter_tCreateLedAdapter(void){GpioAdapter_t adapter;adapter.write_pinGD32_WritePin_Impl;returnadapter;}// 应用层永远调用 adapter.write_pin(1)感受不到底层是GD32还是ST。优势这是代码跨平台移植的利器。总结嵌入式开发怎么选设计模式核心价值嵌入式推荐场景资源消耗CPU/RAM单例模式唯一性管理系统配置、日志、唯一外设极低工厂模式创建与使用分离多型号传感器/屏幕驱动抽象低仅函数指针观察者模式同步一对多通知按键触发联动、简单中断分发低维护回调数组状态模式复杂状态逻辑解耦通信协议、菜单、电源管理中需维护多个状态结构体发布-订阅异步完全解耦带RTOS的多任务通信队列较高需RAM做消息队列适配器模式接口转换HAL层移植、老代码适配新库极低纯封装如果你是新手建议优先掌握 适配器模式用来封装外设驱动和 状态模式用来写按键/协议逻辑这两者立竿见影。等项目引入RTOS后发布-订阅消息队列自然就会成为你的主力工具。