1. 状态模式基础认知
第一次接触状态模式是在重构一个老旧的订单系统时。那个系统里充斥着各种if-else判断订单状态的代码块,每次新增状态都需要在十几个地方添加条件判断。直到某天在《设计模式》红皮书里看到状态模式的定义:"允许对象在内部状态改变时改变它的行为,使对象看起来似乎修改了它的类"。
简单来说,状态模式的核心思想是把对象的状态抽象成独立类,将状态相关的行为封装到对应的状态类中。这样当对象状态切换时,实际上是在运行时切换不同的状态类实例。举个例子,就像电灯开关有"开"和"关"两种状态,每种状态下按下开关的行为不同——在状态模式中,我们会把"开状态"和"关状态"分别实现为两个类。
与策略模式不同,状态模式中的状态类之间是彼此知晓的,通常会持有上下文对象的引用。而策略模式的各个策略类则是完全独立的。这种差异决定了状态模式更适合处理状态转移明确的场景。
2. 经典状态机实现方案
2.1 基础类结构设计
一个完整的状态模式实现通常包含三个关键组件:
- Context(上下文):维护当前状态实例的引用
- State(抽象状态):定义状态接口
- ConcreteState(具体状态):实现特定状态下的行为
以订单系统为例,我们先定义抽象状态接口:
class OrderState { public: virtual ~OrderState() = default; virtual void ship(OrderContext* context) = 0; virtual void cancel(OrderContext* context) = 0; virtual void refund(OrderContext* context) = 0; };然后实现具体状态类。比如待支付状态:
class PendingPaymentState : public OrderState { public: void ship(OrderContext* context) override { throw std::runtime_error("Cannot ship unpaid order"); } void cancel(OrderContext* context) override { context->setState(std::make_unique<CancelledState>()); } void refund(OrderContext* context) override { throw std::runtime_error("Cannot refund unpaid order"); } };2.2 状态转移控制
上下文类负责管理状态转换:
class OrderContext { std::unique_ptr<OrderState> state_; public: explicit OrderContext(std::unique_ptr<OrderState> state) : state_(std::move(state)) {} void ship() { state_->ship(this); } void cancel() { state_->cancel(this); } void refund() { state_->refund(this); } void setState(std::unique_ptr<OrderState> newState) { state_ = std::move(newState); } };这种实现方式将状态转换逻辑分散在各个具体状态类中。当业务复杂时,也可以考虑引入专门的状态转移表来集中管理。
3. 现代C++实现技巧
3.1 使用std::variant实现类型安全状态
C++17引入的variant可以用来实现无继承的状态模式:
using State = std::variant<PendingPayment, Paid, Shipped, Delivered>; class Order { State state_; template<typename T> void transitionTo(T&& newState) { state_ = std::forward<T>(newState); } public: void pay() { std::visit([this](auto&& state) { using T = std::decay_t<decltype(state)>; if constexpr (std::is_same_v<T, PendingPayment>) { transitionTo(Paid{}); } else { throw std::runtime_error("Invalid operation"); } }, state_); } };这种方法避免了虚函数调用开销,且能利用编译时类型检查。
3.2 状态模式与CRTP结合
通过CRTP(奇异递归模板模式)可以消除动态分配开销:
template<typename Derived> class State { public: void ship(Derived* context) { static_cast<Derived*>(this)->shipImpl(context); } // 其他接口方法... }; class PaidState : public State<PaidState> { public: void shipImpl(Order* order) { order->setState<ShippedState>(); } };4. 实战中的优化策略
4.1 状态对象复用
如果状态类是无状态的(只有方法没有成员变量),可以将其实现为单例:
class ShippedState : public OrderState { static ShippedState instance_; public: static ShippedState& instance() { return instance_; } void ship(OrderContext*) override { /*...*/ } // 其他方法... }; // 使用时 context.setState(&ShippedState::instance());4.2 异步状态处理
对于涉及IO操作的状态转换,可以使用协程:
class PaymentProcessingState : public OrderState { asio::awaitable<void> pay(OrderContext* ctx) { auto result = co_await paymentGateway.charge(); if (result.success) { ctx->setState(std::make_unique<PaidState>()); } else { ctx->setState(std::make_unique<PaymentFailedState>()); } } };5. 测试与调试技巧
5.1 状态转换验证
使用GTest验证状态转换:
TEST(OrderStateTest, PaymentTransitionsToPaid) { OrderContext ctx(std::make_unique<PendingPaymentState>()); ctx.pay(); ASSERT_TRUE(dynamic_cast<PaidState*>(ctx.currentState())); }5.2 状态追踪调试
添加状态历史记录功能:
class OrderContext { std::vector<std::string> stateHistory_; public: void setState(std::unique_ptr<OrderState> newState) { stateHistory_.push_back(typeid(*newState).name()); state_ = std::move(newState); } const auto& getStateHistory() const { return stateHistory_; } };6. 性能考量与取舍
状态模式的性能开销主要来自:
- 虚函数调用(每次状态方法调用)
- 状态对象分配/释放(如果每次转换都创建新实例)
优化方案:
- 使用栈分配的状态对象(如variant实现)
- 复用无状态的状态对象
- 在性能关键路径考虑策略模式替代
实测数据显示,对于每秒处理1000+状态转换的系统,使用variant实现比传统虚函数方式快约30%。
7. 与其他模式的协作
7.1 状态模式与观察者模式
状态变化时通知观察者:
class OrderContext : public Observable { void setState(std::unique_ptr<OrderState> newState) { state_ = std::move(newState); notifyObservers(Event::StateChanged); } };7.2 状态模式与工厂模式
通过工厂创建状态对象:
class StateFactory { public: static std::unique_ptr<OrderState> create(StateType type) { switch(type) { case StateType::Paid: return std::make_unique<PaidState>(); // 其他状态... } } };8. 实际案例:游戏角色状态机
实现游戏角色状态转换:
class Character { std::unique_ptr<CharacterState> state_; public: void update() { state_->update(this); } void handleInput(Input input) { state_->handleInput(this, input); } }; class RunningState : public CharacterState { void handleInput(Character* c, Input input) override { if (input == Input::Jump) { c->setState(std::make_unique<JumpingState>()); } } };这种架构使得新增状态(如蹲下、攀爬)不会影响现有代码。