状态模式:有限状态机在电商订单系统中的设计与实现
一、模式核心:用状态切换驱动行为变化
在电商订单系统中,订单状态会随着用户操作动态变化:「已创建」的订单支付后变为「已支付」,发货后变为「已发货」,不同状态下的操作权限和业务逻辑差异巨大。传统方式通过大量if-else
判断状态,导致代码臃肿且难以维护。状态模式(State Pattern) 通过将状态封装为独立类,使对象在不同状态下自动切换行为,核心解决:
- 状态驱动行为:不同状态对应不同操作逻辑,避免海量条件判断
- 状态转换可控:集中管理状态迁移规则,确保状态变化符合业务流程
核心思想与 UML 类图
二、核心实现:构建可扩展的订单状态机
1. 定义状态接口(封装状态相关操作)
public interface OrderState {
// 支付操作:不同状态下支付逻辑不同
void pay(OrderContext context);
// 发货操作:仅特定状态允许发货
void deliver(OrderContext context);
// 取消操作:不同状态下取消流程不同
void cancel(OrderContext context);
}
2. 实现具体状态类(封装各状态的行为)
已创建状态(允许支付和取消)
public class CreatedState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("订单创建状态:执行支付流程...");
context.setCurrentState(new PaidState()); // 切换到已支付状态
System.out.println("状态变更:已创建 → 已支付");
}
@Override
public void deliver(OrderContext context) {
throw new IllegalStateException("错误:未支付订单不能发货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单创建状态:执行取消流程(无需扣款)");
context.setCurrentState(new CanceledState()); // 切换到已取消状态
}
}
已支付状态(允许发货和取消)
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
throw new IllegalStateException("错误:订单已支付,请勿重复支付");
}
@Override
public void deliver(OrderContext context) {
System.out.println("订单支付状态:执行发货流程...");
context.setCurrentState(new DeliveredState()); // 切换到已发货状态
System.out.println("状态变更:已支付 → 已发货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单支付状态:执行取消流程(需退款)");
context.setCurrentState(new CanceledState());
}
}
3. 上下文类(管理状态切换与状态相关数据)
public class OrderContext {
private OrderState currentState;
private final String orderId;
public OrderContext(String orderId) {
this.orderId = orderId;
this.currentState = new CreatedState(); // 初始状态为已创建
}
// 状态切换入口
public void setCurrentState(OrderState state) {
this.currentState = state;
}
// 对外暴露的业务操作,委托给当前状态处理
public void pay() {
currentState.pay(this);
}
public void deliver() {
currentState.deliver(this);
}
public void cancel() {
currentState.cancel(this);
}
}
4. 客户端调用示例(状态流转演示)
public class ClientDemo {
public static void main(String[] args) {
OrderContext order = new OrderContext("ORDER_1001");
// 支付操作:创建状态 → 支付状态
order.pay(); // 输出:支付流程 & 状态变更
// 发货操作:支付状态 → 发货状态
order.deliver(); // 输出:发货流程 & 状态变更
// 尝试重复支付(已支付状态不允许)
try {
order.pay();
} catch (IllegalStateException e) {
System.out.println("异常:" + e.getMessage()); // 输出错误信息
}
}
}
三、进阶:构建健壮的状态机框架
1. 状态工厂(集中管理状态实例)
public class OrderStateFactory {
private static final Map<StateType, OrderState> STATE_POOL = new EnumMap<>(StateType.class);
static {
STATE_POOL.put(StateType.CREATED, new CreatedState());
STATE_POOL.put(StateType.PAID, new PaidState());
// 注册所有状态类
}
public static OrderState getState(StateType type) {
return STATE_POOL.get(type);
}
}
// 使用枚举定义状态类型(避免魔法值)
enum StateType {
CREATED, PAID, DELIVERED, CANCELED
}
2. 状态转换校验(防止非法状态迁移)
public abstract class BaseOrderState implements OrderState {
// 定义合法的状态转换规则
protected abstract Set<StateType> allowedNextStates();
@Override
public final void transitionTo(OrderContext context, StateType nextState) {
if (allowedNextStates().contains(nextState)) {
context.setCurrentState(OrderStateFactory.getState(nextState));
} else {
throw new IllegalArgumentException(
"非法状态转换:当前状态" + getCurrentState() + "不能转换为" + nextState
);
}
}
}
// 具体状态类实现合法转换规则
public class CreatedState extends BaseOrderState {
@Override
protected Set<StateType> allowedNextStates() {
return Set.of(StateType.PAID, StateType.CANCELED); // 仅允许支付或取消
}
}
3. 可视化状态机(状态流转图)
四、框架与源码中的状态模式实践
1. Spring State Machine(专业状态机框架)
-
核心组件:
StateMachine
:管理状态和转换Transition
:定义状态转换条件(如支付成功触发状态变更)
-
使用示例:
// 定义订单状态和事件 StateMachine<OrderState, OrderEvent> stateMachine = StateMachineBuilder .<OrderState, OrderEvent>builder() .withStates() .initial(OrderState.CREATED) .state(OrderState.PAID) .end(OrderState.CANCELED, OrderState.COMPLETED) .withTransitions() .from(OrderState.CREATED).to(OrderState.PAID).on(OrderEvent.PAY) .build(); stateMachine.sendEvent(OrderEvent.PAY); // 触发状态转换
2. MyBatis 事务状态管理
Executor
接口根据事务状态(自动提交 / 手动提交)切换执行逻辑- 通过
BaseExecutor
的子类(如SimpleExecutor
、BatchExecutor
)实现不同状态下的行为
3. TCP 连接状态(Java NIO 实现)
SelectionKey
的状态(连接、可读、可写)通过状态模式管理事件分发- 避免大量
if (key.isReadable())
类型的条件判断
五、避坑指南:正确使用状态模式的 3 个要点
1. 避免状态爆炸(控制状态数量)
- ❌ 反模式:为每个细小状态创建独立类(如订单的「支付中」「发货中」)
- ✅ 最佳实践:
- 合并相似状态(如「待审核」「审核中」合并为「审核状态」)
- 使用状态工厂 + 枚举统一管理状态实例
2. 处理状态转换的原子性
- 在分布式系统中,状态变更需结合分布式锁或事务保证原子性
// 分布式场景下的状态转换(伪代码)
public void safeTransition(OrderContext context, StateType nextState) {
String lockKey = "order_state_lock:" + context.getOrderId();
try (RedissonLock lock = redisson.getLock(lockKey)) {
lock.lock();
currentState.transitionTo(context, nextState);
}
}
3. 状态类的职责单一性
- 状态类应专注于状态相关行为,避免包含业务逻辑之外的代码
- 复杂业务逻辑可提取为独立服务(如
PaymentService
、DeliveryService
)
六、总结:何时该用状态模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
对象状态驱动行为 | 不同状态下操作逻辑差异大,且状态可枚举 | 订单状态机、电梯控制系统、工作流引擎 |
状态转换规则复杂 | 需要集中管理合法的状态迁移路径 | 游戏角色状态(战斗 / 待机 / 死亡)、设备状态(开机 / 待机 / 关机) |
避免海量条件判断 | 拒绝if-else 地狱,追求代码可维护性 | 编译器状态(词法分析 / 语法分析 / 语义分析) |
状态模式通过「状态封装 + 行为委托」的设计,将状态相关的复杂性从业务逻辑中剥离,使系统在面对状态变化时更具弹性。下一篇我们将深入探讨责任链模式,解析从 Sentinel 流控到审批流程的链式处理逻辑,敬请期待!
扩展思考:状态模式 vs 策略模式
两者都通过「封装变化」实现行为切换,但核心目标不同:
模式 | 变化维度 | 状态关联 | 典型应用 |
---|---|---|---|
状态模式 | 对象的状态(动态变化) | 状态之间存在依赖和转换 | 状态机驱动的业务流程 |
策略模式 | 算法或策略的选择(静态替换) | 无状态依赖,策略间独立 | 不同排序算法、支付方式选择 |
理解这种差异,能帮助我们在设计时更精准地选择合适的模式。