状态模式(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。而且不同状态之间的转换关系,散落在各个分支里,根本看不清楚。
用状态模式重构订单状态机
我们先画一张图,看看订单状态机的完整流转:
这张图里,每个状态只知道自己能转到哪些状态。新建状态知道可以支付或取消,已支付状态知道可以发货或取消。状态之间的转换逻辑,封装在每个状态类内部。
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