嵌入式C语言编程:策略模式、状态模式和状态机的应用
1. 概述
在没有面向对象语法的C语言中,策略(Strategy)模式和状态(State)模式都通过“上下文 + 接口”组合来模拟多态。
它们在代码结构上几乎一致,但设计意图和应用场景却差异很大。
本文将结合典型的滤波算法和USB设备示例,深入解析两者的实现差异与选型建议。
2. 两种模式介绍
- 策略模式:将一组算法封装起来,调用者在运行时决定使用哪一种,关注“如何做”。
策略模式类图
- 状态模式:让对象在内部状态变化时自行切换行为,关注“何时做、做什么”。
状态模式类图
状态转换图
3. 实现结构其实类似
// 通用接口类型(vtable)
typedef struct {void (*action)(void *ctx, int evt);
} module_iface_t;// 通用上下文
typedef struct {const module_iface_t *iface; // 指向当前策略或状态void *state_data; // 具体实例数据
} module_ctx_t;
上述结构在策略和状态模式下均可重用:仅需替换 action
策略函数或 handle_event
状态函数。
4. 策略模式详解
4.1 典型场景
一个传感器需要根据功耗或精准度要求,动态选用不同滤波算法。
4.2 代码示例
// 策略接口
typedef struct {void (*filter)(int *buf, int len);
} filter_strategy_t;// 上下文
typedef struct {const filter_strategy_t *strat;int data[128];
} sensor_t;// 均值滤波
void mean_filter(int *buf, int len) { /* ... */ }
const filter_strategy_t mean_strat = { .filter = mean_filter };// 卡尔曼滤波
void kalman_filter(int *buf, int len) { /* ... */ }
const filter_strategy_t kalman_strat = { .filter = kalman_filter };// 应用层切换策略
void sensor_process(sensor_t *s) {if (is_low_power()) {s->strat = &mean_strat;} else {s->strat = &kalman_strat;}s->strat->filter(s->data, 128);
}
4.3 策略模式特点
- 切换由外部决定
- 上下文仅负责调用,不关心何时切换
- 适合算法族、可插拔的行为
5. 状态模式详解
5.1 典型场景
一个USB设备在不同生命周期阶段:断开、连接中、就绪、错误,各阶段有不同处理逻辑,并能自动流转。
5.2 代码示例
typedef struct usb_dev_s usb_dev_t;// 状态接口
typedef struct {void (*handle)(usb_dev_t *dev, int evt);
} usb_state_t;// 上下文
struct usb_dev_s {const usb_state_t *state;// 设备相关数据
};// 断开状态
void st_disconnected(usb_dev_t *d, int evt) {if (evt == EVT_PLUG_IN) {d->state = &st_connecting;start_enumeration();}
}
const usb_state_t st_disconnected = { .handle = st_disconnected };// 连接中状态
void st_connecting(usb_dev_t *d, int evt) {if (evt == EVT_ENUM_OK) {d->state = &st_ready;} else if (evt == EVT_ENUM_FAIL) {d->state = &st_error;}
}
const usb_state_t st_connecting = { .handle = st_connecting };// 事件分发
void usb_event(usb_dev_t *d, int evt) {d->state->handle(d, evt);
}
6. 策略模式与状态模式的状态机化融合
6.1 概述
实现了策略模式与状态模式的深度融合,既能在运行时无缝切换不同流程,也能在状态内部灵活替换子算法,为复杂嵌入式控制系统提供高内聚、低耦合、可配置的可复用框架。
在嵌入式系统(如固件升级、多协议网关、自适应控制)中,通常面临两类变化点:
- 横向变化:整条业务流程需要整体替换(例如升级流程与正常流程)。
- 纵向变化:流程内部某一状态的子算法需要动态替换(例如根据网络质量切换可靠传输与快速传输)。
如果采用传统的switch-case
实现,常会引发以下困境:
- 新增流程时需要阅读理解整份代码。
- 运行时切换流程通常需重启或清零全局变量,造成业务中断。
- 难以针对单一流程进行单元测试证。
6.2 策略-状态两级抽象
实现了策略模式与状态模式的深度融合,既能在运行时无缝切换不同流程,也能在状态内部灵活替换子算法,为复杂嵌入式控制系统提供高内聚、低耦合、可配置的可复用框架。
第一级:整状态机作为策略
将完整状态机(含状态表、变量、定时器与转移逻辑)封装为统一策略接口 sm_strategy_t
。
Context 在运行时只需持有对该接口的指针,即可灵活切换整个状态机实现。
sm_ctx_t fsm;
fsm.ops = use_upgrade ? &sm_upgrade : &sm_normal;
fsm.ops->init(&fsm);
第二级:状态内部再嵌策略
在某个具体状态的处理函数中,根据运行时条件动态注入子策略,以替换子算法或业务动作。
/* 在 Transferring 状态中,根据网络质量选择子策略 */
ctx->sub = is_lossy() ? &reliable_xfer_strategy : &fast_xfer_strategy;
ctx->sub->chunk_size = calc_chunk_size(ctx->rtt);
ctx->sub->send(ctx->sub, evt);
两级策略各自独立:
- 第一级解决“流程级”整体切换。
- 第二级解决“状态级”内部微调。
6.3 静态结构
6.4 运行时视图
6.5 关键实现细节
策略接口定义
typedef struct sm_ctx_s sm_ctx_t;
typedef struct {void (*init)(sm_ctx_t*);void (*dispatch)(sm_ctx_t*, int);
} sm_strategy_t;
运行时上下文
struct sm_ctx_s {const sm_strategy_t *ops; /* 当前流程策略 */void *state_data;
};
零停机切换
通过原子写入指针即可完成业务流程切换,无需中断或重启。
void switch_strategy(sm_ctx_t *ctx, const sm_strategy_t *new_ops)
{new_ops->init(ctx);ctx->ops = new_ops; /* 原子赋值,零停机 */
}
状态内子策略示例
typedef struct {void (*send)(const void *self, int event);size_t chunk_size;
} xfer_strategy_t;static void st_transferring(sm_ctx_t *ctx, int evt)
{xfer_strategy_t *sub = select_xfer_strategy(ctx->rtt);sub->chunk_size = calc_chunk(ctx->rtt);sub->send(sub, evt);
}