c++注意点(15)----状态模式
1. 核心定义
状态模式(State Pattern):
当一个对象的内部状态改变时,它的行为也随之改变,看起来就好像这个对象改变了它的类。
它的目标是 把状态的行为封装到独立的类/函数里,对象会在运行过程中切换引用的状态对象而改变自身行为。
2.为什么要用状态模式(State Pattern)
状态模式的核心目标是 管理对象在不同状态下的行为,并且 避免臃肿的条件分支。
背后的问题:
在很多程序(尤其是嵌入式设备、UI逻辑、协议处理)里,都有“状态”这个概念:
- 一个设备可能处于 初始化 / 运行 / 故障 等状态
- 每个状态下的操作(事件响应)不同
- 如果我们直接用 switch-case 或 if-else 来判断状态,那么所有状态的逻辑会混在一起
- 维护困难
- 状态变多时
switch
会变得非常庞大 - 扩展新状态需要改原有代码
状态模式就是为了解决这个可维护性和扩展性问题:
- 把状态对应的行为封装成独立模块(类/结构/函数)
- 当状态变化时,只需要修改当前引用的状态对象,不需要在大量的分支中找逻辑
3.状态模式的优点
- 遵循开闭原则
不修改已有状态类,就能增加新状态(只是多一个实现类/函数)。
- 清晰分离状态逻辑
每个状态的代码放在独立文件/类里,不会让 switch 把所有逻辑混在一起。
- 运行时灵活切换状态
可以根据条件在运行中替换当前状态对象,立刻获得另一套行为。
- 便于单元测试
每个状态可以单独测试,而不用在庞大的分支里模拟环境。
- 可与多态结合(在 C++/Java 里)
用多态让调用方无需关心当前状态类型,直接调用统一接口。
4.状态模式和状态机的关系与区别
状态模式 ≠ 状态机,但它们关系很密切。
状态模式是一种“面向对象的设计方法”,状态机是一种“系统建模与逻辑执行方式”。
对比项 | 状态模式 | 状态机 |
---|---|---|
定义 | 把对象的状态和对应行为封装成独立类/对象 | 使用图/表表示系统的状态及状态之间的迁移 |
目的 | 避免复杂分支结构,使代码更易维护和扩展 | 精确描述和控制状态变化逻辑 |
实现形式 | 面向对象语言通常用多态;C 可用函数指针 | 既可用多态,也可用 switch 、表驱动等 |
关注点 | 封装行为,减少依赖 | 状态、事件、迁移过程控制 |
场景 | OOP 业务逻辑设计 | 嵌入式设备控制、协议解析、工作流程 |
联系:
- 一个状态机可以用“状态模式”来实现
- 状态模式是实现状态机的一种优雅方法
- 像 QP 框架就是在状态模式的基础上做了扩展:不仅封装状态行为,而且支持层次化状态机(HSM)、时间事件、事件队列等。
5. 和 switch-case 的区别
虽然 switch
也能实现状态控制,但差别很大:
- 优点:简单、上手快
- 缺点:
- 所有状态逻辑集中在一个函数中,代码会越来越庞大
- 新增状态需要修改这个
switch
结构 → 破坏开闭原则 - 状态切换和行为执行往往耦合在一起,维护不方便
- 不适合多人协作,每个状态都得修改同一个文件
用状态模式实现
c版本
#include <stdio.h>/* 状态接口函数指针定义 */
typedef struct State {void (*handle)(struct State **ctx); // 事件处理函数
} State;/* 前向声明状态对象 */
void Init_handle(State **ctx);
void Run_handle(State **ctx);
void Error_handle(State **ctx);/* 状态对象定义 */
State InitState = { Init_handle };
State RunState = { Run_handle };
State ErrorState = { Error_handle };/* 状态机上下文(持有当前状态指针) */
typedef struct {State *current;
} Context;/* 状态切换方法 */
void changeState(Context *ctx, State *newState) {ctx->current = newState;
}/* 状态具体处理逻辑 */
void Init_handle(State **ctx) {printf("[INIT] 初始化完成,切到运行模式\n");*ctx = &RunState;
}void Run_handle(State **ctx) {printf("[RUN] 设备正在运行,出现故障!\n");*ctx = &ErrorState;
}void Error_handle(State **ctx) {printf("[ERROR] 错误处理完成,返回初始化\n");*ctx = &InitState;
}/* 测试入口 */
int main(void) {Context ctx = { &InitState };for (int i = 0; i < 6; i++) {ctx.current->handle(&ctx.current); // 按一次按钮}return 0;
}
c++版本
#include <iostream>
using namespace std;class Context; // 前向声明/* 抽象状态接口 */
class State {
public:virtual ~State() {}virtual void handle(Context* ctx) = 0;
};/* 上下文类,持有当前状态 */
class Context {
public:Context(State* s) : current(s) {}void setState(State* s) { current = s; }void request() { current->handle(this); }
private:State* current;
};/* 具体状态类 */
class InitState : public State {
public:void handle(Context* ctx) override;
};
class RunState : public State {
public:void handle(Context* ctx) override;
};
class ErrorState : public State {
public:void handle(Context* ctx) override;
};/* 状态方法实现 */
void InitState::handle(Context* ctx) {cout << "[INIT] 初始化完成,切到运行模式\n";static RunState run;ctx->setState(&run);
}
void RunState::handle(Context* ctx) {cout << "[RUN] 设备正在运行,出现故障!\n";static ErrorState err;ctx->setState(&err);
}
void ErrorState::handle(Context* ctx) {cout << "[ERROR] 错误处理完成,返回初始化\n";static InitState init;ctx->setState(&init);
}/* 测试入口 */
int main() {static InitState init; // 初始状态Context ctx(&init);for (int i = 0; i < 6; i++) {ctx.request();}return 0;
}
- 优点:
- 每个状态只关心自己的逻辑,代码分散管理
- 扩展新状态只要加一个类/结构,不用改其他状态代码
- 更容易阅读和维护
- 缺点:
- 相比单个函数,要定义更多对象(初期代码量稍大)