状态模式(State):状态转换、订单状态机、C++状态模式实现

状态模式,说白了就是让对象在内部状态改变时,行为也跟着变。听起来像不像一个多面人?嗯,其实我们每天都在用——比如你手机上的闹钟,关闭状态按一下是设置,响铃状态按一下是贪睡,闹钟状态按一下是关闭。同一个按钮,不同状态不同行为。

我最早接触状态模式,是在做一个订单系统的重构。那时候代码里全是if-else,判断订单状态,然后执行不同逻辑。一个方法几百行,改一个状态要翻遍整个文件。后来用了状态模式,整个世界清静了。

状态模式的核心思想

状态模式把每个状态封装成一个独立的类。状态类里定义了该状态下允许的行为。上下文对象持有一个当前状态对象的引用,当状态变化时,替换这个引用。

说白了就是:把条件判断变成了多态调用

核心角色:

  • Context(上下文):持有当前状态对象,对外提供统一的接口
  • State(抽象状态):定义所有状态下共有的行为接口
  • ConcreteState(具体状态):实现特定状态下的行为逻辑

你想想看,如果不用状态模式,你写一个订单状态机,大概长这样:

// 不用状态模式的写法
void processOrder(Order& order, Event event) {
    if (order.getStatus() == "NEW") {
        if (event == PAY) {
            order.setStatus("PAID");
            // 处理支付逻辑
        } else if (event == CANCEL) {
            order.setStatus("CANCELED");
            // 处理取消逻辑
        }
    } else if (order.getStatus() == "PAID") {
        if (event == SHIP) {
            order.setStatus("SHIPPED");
            // 处理发货逻辑
        }
        // ... 更多状态
    }
}

这种代码我见过太多了。每次加一个新状态,就要改这个巨大的if-else。而且不同状态之间的转换关系,散落在各个分支里,根本看不清楚。

用状态模式重构订单状态机

我们先画一张图,看看订单状态机的完整流转:

订单状态机流转图 新建(NEW) 已支付(PAID) 已发货(SHIPPED) 已完成(DONE) 已取消(CANCELED) 已退款(REFUNDED) 支付 发货 确认收货 取消 取消 退货 退款 图例: 正常流转 取消/退货流转 每个状态只关心自己能做什么,不能做什么。状态之间的转换由状态类自己控制。

这张图里,每个状态只知道自己能转到哪些状态。新建状态知道可以支付或取消,已支付状态知道可以发货或取消。状态之间的转换逻辑,封装在每个状态类内部。

C++实现:订单状态机

好了,我们直接上代码。我个人习惯用C++11的智能指针来管理状态对象的生命周期,避免手动delete的麻烦。

#include <iostream>
#include <memory>
#include <string>

// 前向声明
class OrderContext;
class OrderState;

// 抽象状态类
class OrderState {
public:
    virtual ~OrderState() = default;
    
    // 每个状态下的行为
    virtual void pay(std::shared_ptr<OrderContext> context) = 0;
    virtual void ship(std::shared_ptr<OrderContext> context) = 0;
    virtual void confirm(std::shared_ptr<OrderContext> context) = 0;
    virtual void cancel(std::shared_ptr<OrderContext> context) = 0;
    virtual void refund(std::shared_ptr<OrderContext> context) = 0;
    
    virtual std::string getStatusName() const = 0;
};

// 上下文类
class OrderContext : public std::enable_shared_from_this<OrderContext> {
private:
    std::shared_ptr<OrderState> currentState_;
    std::string orderId_;
    
public:
    OrderContext(const std::string& id) 
        : orderId_(id) {
        // 初始状态为新建
        currentState_ = std::make_shared<NewState>();
    }
    
    void setState(std::shared_ptr<OrderState> state) {
        currentState_ = state;
        std::cout << "订单 " << orderId_ 
                  << " 状态变更为: " << currentState_->getStatusName() 
                  << std::endl;
    }
    
    std::string getOrderId() const { return orderId_; }
    
    // 对外接口,委托给当前状态
    void pay() { currentState_->pay(shared_from_this()); }
    void ship() { currentState_->ship(shared_from_this()); }
    void confirm() { currentState_->confirm(shared_from_this()); }
    void cancel() { currentState_->cancel(shared_from_this()); }
    void refund() { currentState_->refund(shared_from_this()); }
};

这里有个细节要注意:shared_from_this() 的使用。因为状态类需要修改上下文的状态,所以必须持有上下文的智能指针。但直接传this指针是不安全的,必须通过enable_shared_from_this来获取。

我曾经踩过的坑: 在状态类的pay方法里,直接传了this指针给另一个状态类。结果上下文被提前析构了,程序崩溃。后来改用shared_from_this,问题解决。记住:谁持有谁负责,生命周期要统一管理

接下来是具体状态类的实现。我们以新建状态为例:

// 新建状态
class NewState : public OrderState {
public:
    void pay(std::shared_ptr<OrderContext> context) override {
        std::cout << "订单 " << context->getOrderId() 
                  << " 已支付" << std::endl;
        context->setState(std::make_shared<PaidState>());
    }
    
    void ship(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:未支付的订单不能发货!" << std::endl;
    }
    
    void confirm(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:未支付的订单不能确认收货!" << std::endl;
    }
    
    void cancel(std::shared_ptr<OrderContext> context) override {
        std::cout << "订单 " << context->getOrderId() 
                  << " 已取消" << std::endl;
        context->setState(std::make_shared<CanceledState>());
    }
    
    void refund(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:未支付的订单不能退款!" << std::endl;
    }
    
    std::string getStatusName() const override {
        return "新建";
    }
};

// 已支付状态
class PaidState : public OrderState {
public:
    void pay(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:订单已支付,不能重复支付!" << std::endl;
    }
    
    void ship(std::shared_ptr<OrderContext> context) override {
        std::cout << "订单 " << context->getOrderId() 
                  << " 已发货" << std::endl;
        context->setState(std::make_shared<ShippedState>());
    }
    
    void confirm(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:未发货的订单不能确认收货!" << std::endl;
    }
    
    void cancel(std::shared_ptr<OrderContext> context) override {
        std::cout << "订单 " << context->getOrderId() 
                  << " 已取消,等待退款" << std::endl;
        context->setState(std::make_shared<CanceledState>());
    }
    
    void refund(std::shared_ptr<OrderContext> context) override {
        std::cout << "错误:请先取消订单再退款!" << std::endl;
    }
    
    std::string getStatusName() const override {
        return "已支付";
    }
};

你发现没有?每个状态类只实现了自己允许的行为。不允许的行为直接打印错误信息。这样代码的意图非常清晰——一个状态类就是一个完整的状态机片段

剩下的状态类(已发货、已完成、已取消、已退款)实现思路完全一样,我就不贴完整代码了。我们直接看使用效果:

int main() {
    auto order = std::make_shared<OrderContext>("ORD-2024-001");
    
    order->pay();      // 新建 -> 已支付
    order->ship();     // 已支付 -> 已发货
    order->confirm();  // 已发货 -> 已完成
    
    // 测试非法操作
    order->cancel();   // 错误:已完成订单不能取消
    
    // 测试取消流程
    auto order2 = std::make_shared<OrderContext>("ORD-2024-002");
    order2->pay();     // 新建 -> 已支付
    order2->cancel();  // 已支付 -> 已取消
    order2->refund();  // 已取消 -> 已退款
    
    return 0;
}

输出结果:

订单 ORD-2024-001 状态变更为: 已支付
订单 ORD-2024-001 状态变更为: 已发货
订单 ORD-2024-001 状态变更为: 已完成
错误:已完成订单不能取消!
订单 ORD-2024-002 状态变更为: 已支付
订单 ORD-2024-002 状态变更为: 已取消
订单 ORD-2024-002 状态变更为: 已退款

状态模式 vs 策略模式

很多初学者会把状态模式和策略模式搞混。我简单说下区别:

对比维度 状态模式 策略模式
核心目的 管理状态转换,状态之间有关联 封装算法,策略之间相互独立
状态/策略关系 状态之间可以互相转换 策略之间完全独立,不互相影响
调用者感知 调用者不感知状态变化 调用者主动选择策略
典型场景 订单状态机、工作流引擎 排序算法选择、支付方式选择

说白了,策略模式是调用者选算法,状态模式是对象自己变行为

状态模式的优缺点

优点:

  • 消除了庞大的条件分支语句
  • 每个状态独立成类,符合单一职责原则
  • 新增状态非常容易,不需要修改现有代码
  • 状态转换逻辑集中管理,不会散落在各处

缺点:

  • 状态类数量会增多,如果状态很多,类会膨胀
  • 状态类之间可能有依赖关系(比如取消状态需要知道退款状态)
  • 如果状态转换规则频繁变化,维护成本会上升

实际项目中的经验

我在做电商订单系统时,一开始用了状态模式,后来发现状态太多了——待支付、已支付、已发货、已签收、已完成、已取消、已退款、售后中、退款中... 十几个状态。每个状态类里都有大量重复的错误处理代码。

后来我做了个优化:引入一个基类,把通用的错误处理逻辑提取出来。比如所有状态下的非法操作,统一打印错误信息。这样每个具体状态类只需要关注自己允许的操作。

还有一个坑:状态对象的创建频率。如果每次状态转换都new一个新对象,在高并发场景下会有性能问题。我建议用享元模式配合状态模式,把状态对象做成单例或者池化。这样状态转换时只需要切换指针,不需要创建新对象。

嗯,说到这个,我记得有一次线上事故,就是因为状态对象频繁创建导致内存飙升。后来改成享元模式,问题就解决了。

总结

状态模式的核心价值在于:把复杂的状态转换逻辑从业务代码中剥离出来,让每个状态自己管理自己的行为。它特别适合那些状态多、转换规则复杂的场景。

你想想看,如果你的代码里到处都是if (status == XXX),而且这些判断散落在各个方法里,那就是时候考虑用状态模式重构了。

最后送大家一句话:好的设计不是一蹴而就的,而是在不断重构中打磨出来的。状态模式就是一个很好的重构工具。


公众号:蓝海资料掘金营,微信deep3321