状态模式(State Pattern)
C++ 状态模式(State Pattern)
一、模式概述
状态模式(State Pattern)属于行为型设计模式。
核心思想
将对象的不同状态分别封装为独立类,对象行为随内部状态改变而自动变化,对外表现如同修改了对象所属类;彻底替代传统大量if-else/switch分支判断,让状态逻辑解耦、职责清晰。
适用场景
- 对象行为依赖自身状态,运行时状态改变则行为随之改变;
- 业务中存在大量基于状态的条件判断代码,分支臃肿、难以维护;
- 状态数量固定、状态转换规则明确,需要扩展新状态;
- 典型业务:电梯状态、订单流程、网络连接、游戏角色、设备运行状态机等。
二、核心角色
状态模式由三类核心角色组成,各司其职:
| 角色 | 名称 | 职责说明 |
|---|---|---|
| 抽象状态 | State | 定义所有状态统一的行为接口,声明状态处理方法,通常为纯虚类 |
| 具体状态 | ConcreteStateX | 继承抽象状态,实现当前状态独有行为;负责触发状态跳转 |
| 上下文 | Context | 持有当前状态对象;对外提供统一调用入口;提供状态切换接口,串联整个状态流程 |
角色协作流程
- 客户端调用上下文的对外接口;
- 上下文将请求委托给当前具体状态对象处理;
- 具体状态执行自身逻辑,并根据业务规则修改上下文的状态;
- 下次请求会交由新状态处理,实现行为自动切换。
三、模式优缺点
优点
- 消除分支语句:剥离大量
if-else/switch,代码结构更整洁; - 符合开闭原则:新增状态只需新增具体状态类,无需修改原有代码;
- 状态逻辑隔离:每个状态的行为、转换逻辑内聚在对应类中,职责单一,易维护;
- 状态转换规范化:转换规则统一管理,流程清晰可控。
缺点
- 类数量膨胀:状态越多,对应的具体状态类就越多,增加代码体量;
- 流程分散:状态跳转逻辑分散在各个状态类中,整体状态流转流程阅读难度提升;
- 过度设计:简单状态(仅2~3个状态、逻辑简单)使用该模式会增加复杂度。
四、C++ 基础实现(标准写法)
1. 前置说明
- 使用纯虚类作为抽象状态基类;
- 采用原始指针演示基础逻辑,后文补充智能指针工程写法;
- 类之间存在交叉引用,使用前向声明解决头文件循环依赖。
2. 完整代码示例
#include<iostream>usingnamespacestd;// 前向声明上下文类classContext;// ===================== 抽象状态类 State =====================classState{public:// 纯虚函数:状态处理行为,参数为上下文,用于状态切换virtualvoidhandle(Context*context)=0;virtual~State()=default;// 虚析构,保证多态析构安全};// ===================== 具体状态类 A =====================classConcreteStateA:publicState{public:voidhandle(Context*context)override;};// ===================== 具体状态类 B =====================classConcreteStateB:publicState{public:voidhandle(Context*context)override;};// ===================== 上下文类 Context =====================classContext{private:State*m_currentState;// 持有当前状态对象public:// 构造函数:初始化默认状态Context(State*state):m_currentState(state){}// 设置/切换状态voidsetState(State*state){m_currentState=state;}// 对外统一请求接口voidrequest(){if(m_currentState){m_currentState->handle(this);// 委托给当前状态处理}}};// 状态A 实现逻辑:执行行为,并切换到状态BvoidConcreteStateA::handle(Context*context){cout<<"当前处于【状态A】,执行状态A逻辑"<<endl;// 状态跳转:切换为状态Bcontext->setState(newConcreteStateB());}// 状态B 实现逻辑:执行行为,并切换到状态AvoidConcreteStateB::handle(Context*context){cout<<"当前处于【状态B】,执行状态B逻辑"<<endl;// 状态跳转:切换为状态Acontext->setState(newConcreteStateA());}// ===================== 客户端测试 =====================intmain(){// 初始化上下文,默认状态为AContextctx(newConcreteStateA());// 多次调用,观察状态自动切换ctx.request();ctx.request();ctx.request();return0;}3. 运行结果
当前处于【状态A】,执行状态A逻辑 当前处于【状态B】,执行状态B逻辑 当前处于【状态A】,执行状态A逻辑五、C++ 工程级优化(智能指针版)
原始指针容易造成内存泄漏、野指针,正式项目推荐使用std::unique_ptr管理状态对象。
优化后代码
#include<iostream>#include<memory>usingnamespacestd;classContext;// 抽象状态classState{public:virtualvoidhandle(Context*context)=0;virtual~State()=default;};// 具体状态AclassConcreteStateA:publicState{public:voidhandle(Context*context)override;};// 具体状态BclassConcreteStateB:publicState{public:voidhandle(Context*context)override;};// 上下文(使用 unique_ptr 管理状态)classContext{private:unique_ptr<State>m_currentState;public:explicitContext(unique_ptr<State>state):m_currentState(move(state)){}voidsetState(unique_ptr<State>state){m_currentState=move(state);}voidrequest(){if(m_currentState)m_currentState->handle(this);}};voidConcreteStateA::handle(Context*context){cout<<"当前处于【状态A】,执行逻辑"<<endl;context->setState(make_unique<ConcreteStateB>());}voidConcreteStateB::handle(Context*context){cout<<"当前处于【状态B】,执行逻辑"<<endl;context->setState(make_unique<ConcreteStateA>());}// 客户端intmain(){Contextctx(make_unique<ConcreteStateA>());ctx.request();ctx.request();ctx.request();return0;}优势:智能指针自动释放内存,无需手动delete,规避内存泄漏。
六、两种状态切换方式
在实际开发中,状态切换分为两种模式:
1. 状态类内部控制跳转(常用)
- 跳转逻辑写在
ConcreteState::handle中; - 优点:状态自管理,内聚性强;
- 缺点:跳转逻辑分散。
2. 上下文统一控制跳转
- 所有状态转换规则集中写在
Context中; - 优点:整体状态流转一目了然,便于梳理完整流程;
- 缺点:上下文会随状态增多变得臃肿。
七、状态模式 vs 策略模式(易混区分)
两者结构相似,均封装行为、委托执行,核心区别在行为目的与切换逻辑:
| 对比维度 | 状态模式 | 策略模式 |
|---|---|---|
| 核心目的 | 处理对象状态变化,行为随状态自动改变 | 封装多种算法/策略,按需替换算法 |
| 切换触发 | 内部状态流转,自动切换 | 外部客户端主动选择,手动切换 |
| 关系 | 状态之间存在流转、依赖关系 | 各个策略相互独立,无先后流转关系 |
| 典型场景 | 状态机、订单、电梯、设备状态 | 排序算法、支付方式、加密算法 |
八、使用注意事项
- 控制状态数量:状态过多会产生大量子类,若状态超过10个,可考虑改用配置表+枚举实现状态机;
- 循环依赖处理:
Context和State互相引用,必须使用前向声明; - 析构安全:抽象状态基类必须提供虚析构函数,防止多态析构内存泄漏;
- 慎用场景:简单两状态、固定逻辑,不建议强行使用,避免过度设计;
- 多线程场景:多线程下需对
m_currentState加锁,保证状态切换线程安全。
九、总结
- 本质:把状态变成类,用多态替代分支判断;
- 核心三角色:抽象状态、具体状态、上下文;
- C++ 开发优先使用智能指针管理状态对象,保证内存安全;
- 适合复杂状态机场景,简单场景按需取舍。
