模块化编程中的设计模式:单例、工厂、观察者与状态机
说实话,很多嵌入式工程师一听到「设计模式」四个字,第一反应是——那是 Java 那边的事吧?我当年也这么想。直到有一次我接手一个大型的物联网网关项目,代码量从几千行膨胀到十几万行,我才发现,没有模式约束的 C 代码,维护起来简直是一场噩梦。
今天我们就来聊聊 C 语言里最实用的四种设计模式。它们不是理论玩具,而是我实战中反复用到的利器。
一、单例模式:全局唯一,但别滥用
单例模式,说白了就是保证某个模块在整个系统中只有一个实例。在 C 里,我们通常用静态全局变量来实现。
我个人的习惯是:硬件外设驱动、系统时钟管理、日志模块,这些天然就应该只有一个实例的东西,用单例模式非常合适。
经典实现方式
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
typedef struct {
void (*log)(const char *msg);
void (*set_level)(int level);
} Logger;
Logger* logger_get_instance(void);
#endif
// logger.c
#include "logger.h"
static Logger instance = {0};
static int initialized = 0;
static void default_log(const char *msg) {
// 默认输出到串口
printf("[LOG] %s\n", msg);
}
Logger* logger_get_instance(void) {
if (!initialized) {
instance.log = default_log;
instance.set_level = NULL;
initialized = 1;
}
return &instance;
}
什么时候不该用单例?
- 当模块可能有多个实例时(比如多个传感器同型号)
- 当单例导致测试困难时(单元测试里 mock 不掉)
- 当单例隐藏了依赖关系时(代码里到处都能调用,耦合度飙升)
二、工厂模式:把「创建」和「使用」拆开
工厂模式解决的是一个问题:调用者不需要知道具体创建的是哪个对象。你想想看,如果你的代码里到处都是 if (type == A) createA() else if (type == B) createB(),那改一个类型就要改所有地方,多痛苦。
我在项目中遇到过这样一个场景:一个数据采集系统需要支持多种传感器,每种传感器的初始化流程完全不同。用工厂模式之后,新增一种传感器只需要加一个工厂分支,主逻辑完全不用动。
简单工厂实现
// sensor_factory.h
typedef struct Sensor {
void (*init)(void);
int (*read)(void);
} Sensor;
Sensor* sensor_create(int type);
// type: 0=温度, 1=湿度, 2=压力
// sensor_factory.c
#include "sensor_factory.h"
static Sensor temp_sensor = { temp_init, temp_read };
static Sensor humi_sensor = { humi_init, humi_read };
static Sensor press_sensor = { press_init, press_read };
Sensor* sensor_create(int type) {
switch (type) {
case 0: return &temp_sensor;
case 1: return &humi_sensor;
case 2: return &press_sensor;
default: return NULL;
}
}
三、观察者模式:事件驱动的核心
观察者模式,说白了就是「发布-订阅」。一个事件发生,多个订阅者收到通知。这在嵌入式里太常见了——按键按下、数据到达、状态变化,都需要通知多个模块。
我记得有一次做智能家居项目,一个温度传感器数据变化,需要同时更新显示、触发报警、记录日志。如果每个模块都去轮询传感器,那 CPU 就废了。观察者模式完美解决这个问题。
简易实现
// observer.h
#define MAX_OBSERVERS 10
typedef void (*event_handler)(int event_id, void *data);
typedef struct {
event_handler handlers[MAX_OBSERVERS];
int count;
} EventBus;
void eventbus_init(EventBus *bus);
int eventbus_register(EventBus *bus, event_handler handler);
void eventbus_fire(EventBus *bus, int event_id, void *data);
// observer.c
#include "observer.h"
void eventbus_init(EventBus *bus) {
bus->count = 0;
}
int eventbus_register(EventBus *bus, event_handler handler) {
if (bus->count >= MAX_OBSERVERS) return -1;
bus->handlers[bus->count++] = handler;
return 0;
}
void eventbus_fire(EventBus *bus, int event_id, void *data) {
for (int i = 0; i < bus->count; i++) {
if (bus->handlers[i]) {
bus->handlers[i](event_id, data);
}
}
}
四、状态机模式:复杂逻辑的救星
状态机模式,我觉得是嵌入式开发里最实用的模式,没有之一。任何有「状态」的系统——通信协议、按键处理、设备控制——都离不开它。
我刚开始写代码时,喜欢用一堆 if-else 处理状态,结果代码又臭又长,改一个状态要翻半天。后来我学会了用「状态表」来实现状态机,清爽多了。
状态表实现
// state_machine.h
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_ERROR,
STATE_COUNT
} State;
typedef enum {
EVENT_START,
EVENT_STOP,
EVENT_ERROR,
EVENT_COUNT
} Event;
typedef State (*action_fn)(void);
typedef struct {
State current_state;
action_fn actions[STATE_COUNT][EVENT_COUNT];
} StateMachine;
// state_machine.c
#include "state_machine.h"
static State on_start_idle(void) {
printf("Idle -> Running\n");
return STATE_RUNNING;
}
static State on_stop_running(void) {
printf("Running -> Idle\n");
return STATE_IDLE;
}
void sm_init(StateMachine *sm) {
sm->current_state = STATE_IDLE;
// 初始化状态表,未定义的事件返回当前状态
for (int s = 0; s < STATE_COUNT; s++)
for (int e = 0; e < EVENT_COUNT; e++)
sm->actions[s][e] = NULL;
sm->actions[STATE_IDLE][EVENT_START] = on_start_idle;
sm->actions[STATE_RUNNING][EVENT_STOP] = on_stop_running;
}
void sm_handle_event(StateMachine *sm, Event event) {
action_fn fn = sm->actions[sm->current_state][event];
if (fn) {
sm->current_state = fn();
}
}
知识体系总览
下面这张图是我自己总结的四种模式的关系。你看,它们解决的是不同层面的问题:
总结一下
这四种模式,我建议你从状态机开始练手,因为它最贴近嵌入式开发的实际需求。然后试试观察者模式,你会发现事件驱动的代码比轮询优雅得多。单例模式和工厂模式,等你项目规模大了自然就会用到。
记住一点:设计模式是工具,不是教条。不要为了用模式而用模式。我见过有人在一个只有三个函数的模块里硬套工厂模式,结果代码量翻了三倍,维护起来更痛苦。合适的才是最好的。