设计模式是软件开发中前人总结的经典“套路”,用于解决特定场景下的代码设计问题。在嵌入式开发中,合理使用设计模式能极大提升代码的可维护性、可扩展性和可移植性。
但要特别注意:嵌入式资源有限(ROM/RAM),不能生搬硬套(比如避免滥用malloc动态内存)。下面我结合C语言(GD32常用) 和嵌入式实战场景,为你详解这6种模式。

1. 单例模式 (Singleton)

**目的:**保证一个类(结构体)在系统中只有一个实例,并提供一个全局访问点。
**嵌入式场景:**管理唯一的硬件资源,如系统日志模块、唯一的外设(如RTC) 或全局配置管理器。

C语言实现(核心技巧:静态局部变量):
// sys_config.h
typedef struct {
    int baud_rate;
    int sys_tick;
} SysConfig_t;

// sys_config.c
SysConfig_t* GetSysConfig(void) {
    // 静态局部变量,存放在静态存储区,只会被初始化一次
    static SysConfig_t instance = {
        .baud_rate = 115200,
        .sys_tick = 0
    };
    return &instance; // 返回统一指针
}

// 使用:任何地方调用 GetSysConfig() 拿到的都是同一个地址
GetSysConfig()->baud_rate = 9600; 
注意事项:

在裸机(无RTOS)下安全;在RTOS多任务下,若涉及写操作,需加互斥锁保护。

2. 工厂模式 (Factory)

**目的:**定义一个用于创建对象(结构体)的接口,让子类/具体模块决定实例化哪一个类。将“创建”和“使用”分离。
**嵌入式场景:**系统挂载了多个不同型号的传感器(如温度传感器有DS18B20、SHT30),应用层不需要知道具体型号,只需向“工厂”要一个“温度传感器对象”。

C语言实现(核心技巧:函数指针 + 条件分支):
// 抽象传感器接口
typedef struct {
    int (*init)(void);
    float (*read)(void);
} SensorOps_t;

// 工厂:根据类型号返回具体的操作函数集
SensorOps_t* SensorFactory_Create(int type) {
    static SensorOps_t ds18b20_ops = {DS18B20_Init, DS18B20_Read};
    static SensorOps_t sht30_ops = {SHT30_Init, SHT30_Read};
    
    switch(type) {
        case TYPE_DS18B20: return &ds18b20_ops;
        case TYPE_SHT30:   return &sht30_ops;
        default: return NULL;
    }
}

// 应用层使用
SensorOps_t* temp_sensor = SensorFactory_Create(TYPE_SHT30);
temp_sensor->init();
float temp = temp_sensor->read();
优势:

新增传感器时,只需修改工厂函数,上层应用代码(init/read调用)完全不用改。

3. 观察者模式 (Observer)

**目的:**定义对象间的一对多依赖关系。当一个对象(主题)状态改变时,所有依赖它的对象(观察者)都会自动收到通知并更新。
**嵌入式场景:**按键中断触发,需要同时通知“LED闪烁模块”、“蜂鸣器模块”和“界面显示模块”。

C语言实现(核心技巧:回调函数链表):
#define MAX_OBSERVERS 5
typedef void (*NotifyCallback)(int event);

typedef struct {
    NotifyCallback callbacks[MAX_OBSERVERS];
    int count;
} Subject_t;

void Subject_Attach(Subject_t* sub, NotifyCallback cb) {
    if (sub->count < MAX_OBSERVERS) sub->callbacks[sub->count++] = cb;
}

void Subject_Notify(Subject_t* sub, int event) {
    for (int i = 0; i < sub->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语言实现(核心技巧:函数指针表):
// 定义状态操作接口
typedef struct StateMachine StateMachine_t;
typedef struct {
    void (*handle)(StateMachine_t* machine, int event);
} State_t;

// 定义状态机上下文
struct StateMachine {
    const State_t* current_state;
    int data;
};

// 具体状态A的handle函数
static void StateA_Handle(StateMachine_t* machine, int event) {
    if (event == EVT_NEXT) {
        printf("State A -> State B");
        machine->current_state = &state_b; // 切换状态
    }
}

// 使用
machine.current_state = &state_a;
machine.current_state->handle(&machine, EVT_NEXT);
优势:

新增“State C”时,无需修改旧状态逻辑,符合“开闭原则”。

5. 发布-订阅模式 (Publish-Subscribe)

**目的:**这是观察者模式的“进阶解耦版”。发布者(Publisher)不直接通知订阅者(Subscriber),而是通过一个中间件(消息总线/事件队列) 转发。发布者和订阅者完全不知道对方的存在。
**嵌入式场景:**RTOS环境下的跨任务通信(如FreeRTOS的消息队列)、GUI框架的事件分发。

区别对比(重点!):

(1)观察者模式:同步,主题持有观察者指针,直接调用回调(两者耦合在一起)。
(2)发布-订阅:异步,通过中间层(队列)传递。发布者只管发消息,订阅者只管收消息。

C语言实现(基于RTOS队列):
// 发布者任务
void Task_Sensor(void *pv) {
    int temp = ReadTemp();
    xQueueSend(g_event_queue, &temp, 0); // 只管发,不管谁收
}

// 订阅者任务1(界面)
void Task_Display(void *pv) {
    int temp;
    while(1) {
        if (xQueueReceive(g_event_queue, &temp, portMAX_DELAY)) {
            UpdateLCD(temp);
        }
    }
}
// 订阅者任务2(存储)
void Task_Storage(void *pv) {
    int temp;
    while(1) {
        if (xQueueReceive(g_event_queue, &temp, portMAX_DELAY)) {
            SaveToSD(temp);
        }
    }
}

6. 适配器模式 (Adapter)

**目的:**将一个类的接口转换成客户希望的另一个接口。解决接口不兼容的问题,让原本不能一起工作的类可以一起工作。
**嵌入式场景:**硬件抽象层(HAL) 的终极体现。例如,你的代码原先是基于ST库的 HAL_GPIO_WritePin,现在要移植到GD32库的 gpio_bit_write,不需要改应用层,只需写一个适配器。

C语言实现(核心技巧:中间层封装):
// 目标接口(应用层期望的)
typedef struct {
    void (*write_pin)(uint8_t level);
} GpioAdapter_t;

// 适配器实现:将GD32特有的API,适配成目标接口
static void GD32_WritePin_Impl(uint8_t level) {
    // 内部调用GD32原厂库函数
    gpio_bit_write(GPIOA, GPIO_PIN_8, (bit_status)level);
}

GpioAdapter_t CreateLedAdapter(void) {
    GpioAdapter_t adapter;
    adapter.write_pin = GD32_WritePin_Impl;
    return adapter;
}

// 应用层:永远调用 adapter.write_pin(1),感受不到底层是GD32还是ST。
优势:

这是代码跨平台移植的利器。

总结:嵌入式开发怎么选?

设计模式核心价值嵌入式推荐场景资源消耗(CPU/RAM)
单例模式唯一性管理系统配置、日志、唯一外设极低
工厂模式创建与使用分离多型号传感器/屏幕驱动抽象低(仅函数指针)
观察者模式同步一对多通知按键触发联动、简单中断分发低(维护回调数组)
状态模式复杂状态逻辑解耦通信协议、菜单、电源管理中(需维护多个状态结构体)
发布-订阅异步完全解耦带RTOS的多任务通信(队列)较高(需RAM做消息队列)
适配器模式接口转换HAL层移植、老代码适配新库极低(纯封装)

如果你是新手,建议优先掌握 适配器模式(用来封装外设驱动)和 状态模式(用来写按键/协议逻辑),这两者立竿见影。等项目引入RTOS后,发布-订阅(消息队列)自然就会成为你的主力工具。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐