状态模式

订单系统的 if-else 越来越多:订单创建后可以支付,支付后才能发货,发货后才能确认收货...各种状态转换规则散落在代码各处,新来的同事每次都要问「这个状态下能执行那个操作吗?」

更糟糕的是,上周线上出了一个 bug:已完成的订单居然还能被取消。原来是某个分支条件写错了,把「已完成」状态的订单错误地加入了可取消列表。

这正是状态模式要解决的问题:当对象的行为取决于其内部状态,且状态转换规则复杂时,如何避免 if-else 爆炸?

问题背景:状态转换的复杂性

订单状态流转是一个典型的状态机问题:

stateDiagram-v2
    [*] --> 待支付: 创建订单
    待支付 --> 已取消: 超时/用户取消
    待支付 --> 待发货: 支付成功
    待发货 --> 已发货: 商家发货
    已发货 --> 待收货: 确认收货
    待收货 --> 已完成: 评价/超时自动完成
    已发货 --> 已取消: 退款
    待收货 --> 已取消: 退款

用 if-else 实现:

public void handle(Order order, String action) {
    // 状态转换规则
    if ("待支付".equals(order.getStatus())) {
        if ("支付".equals(action)) {
            order.setStatus("待发货");
        } else if ("取消".equals(action)) {
            order.setStatus("已取消");
        }
    } else if ("待发货".equals(order.getStatus())) {
        if ("发货".equals(action)) {
            order.setStatus("已发货");
        } else if ("取消".equals(action)) {
            order.setStatus("已取消");
        }
    } else if ("已发货".equals(order.getStatus())) {
        if ("确认收货".equals(action)) {
            order.setStatus("待收货");
        } else if ("退款".equals(action)) {
            order.setStatus("已取消");
        }
    } else if ("待收货".equals(action)) {
        if ("确认收货".equals(action)) {
            order.setStatus("已完成");
        } else if ("退款".equals(action)) {
            order.setStatus("已取消");
        }
    }
    // 每加一个状态就要改这里
}

问题:

  1. 状态和转换规则混在一起,难以维护
  2. 难以保证状态转换的安全性
  3. 添加新状态需要修改多处代码

状态模式结构

状态模式(State Pattern)允许对象在内部状态改变时改变其行为。对象看起来好像修改了其类。

classDiagram
    class Context {
        -State state
        +setState(State)
        +request()
    }
    class State {
        <<interface>>
        +handle(Context)
    }
    class ConcreteStateA {
        +handle(Context)
    }
    class ConcreteStateB {
        +handle(Context)
    }
    class ConcreteStateC {
        +handle(Context)
    }
    Context o--> State
    State <|.. ConcreteStateA
    State <|.. ConcreteStateB
    State <|.. ConcreteStateC

状态接口

public interface OrderState {
    /**
     * 处理订单
     * @param context 订单上下文
     */
    void handle(OrderContext context);

    /**
     * 获取状态名称
     */
    String getStateName();

    /**
     * 当前状态允许的操作
     */
    default List<String> getAllowedActions() {
        return Collections.emptyList();
    }
}

具体状态实现

public class PendingPaymentState implements OrderState {
    @Override
    public void handle(OrderContext context) {
        // 待支付状态的处理逻辑
        context.setState(this);
    }

    @Override
    public String getStateName() {
        return "待支付";
    }

    @Override
    public List<String> getAllowedActions() {
        return Arrays.asList("支付", "取消");
    }

    public void pay(OrderContext context) {
        // 支付逻辑
        context.setState(new PendingShipmentState());
        context.notifyStateChanged("已支付,等待发货");
    }

    public void cancel(OrderContext context) {
        context.setState(new CancelledState());
        context.notifyStateChanged("订单已取消");
    }
}

public class PendingShipmentState implements OrderState {
    @Override
    public void handle(OrderContext context) {
        // 待发货状态的处理逻辑
    }

    @Override
    public String getStateName() {
        return "待发货";
    }

    @Override
    public List<String> getAllowedActions() {
        return Arrays.asList("发货", "取消");
    }
}

public class ShippedState implements OrderState {
    @Override
    public void handle(OrderContext context) {
        // 已发货状态的处理逻辑
    }

    @Override
    public String getStateName() {
        return "已发货";
    }

    @Override
    public List<String> getAllowedActions() {
        return Arrays.asList("确认收货", "退款");
    }
}

public class CompletedState implements OrderState {
    @Override
    public void handle(OrderContext context) {
        // 已完成状态的处理逻辑
    }

    @Override
    public String getStateName() {
        return "已完成";
    }

    @Override
    public List<String> getAllowedActions() {
        return Arrays.asList("评价", "申请售后");
    }
}

public class CancelledState implements OrderState {
    @Override
    public void handle(OrderContext context) {
        // 已取消状态的处理逻辑
    }

    @Override
    public String getStateName() {
        return "已取消";
    }

    @Override
    public List<String> getAllowedActions() {
        return Collections.emptyList();  // 无可用操作
    }
}

Context 上下文

public class OrderContext {
    private OrderState state;
    private final String orderId;
    private final Logger logger = LoggerFactory.getLogger(getClass());

    public OrderContext(String orderId) {
        this.orderId = orderId;
        this.state = new PendingPaymentState();  // 初始状态
    }

    public void setState(OrderState state) {
        this.state = state;
    }

    public OrderState getState() {
        return state;
    }

    public String getOrderId() {
        return orderId;
    }

    /**
     * 执行操作:委托给当前状态处理
     */
    public void execute(String action) {
        logger.info("订单 {} 执行操作: {}", orderId, action);
        state.handle(this);
        // 根据操作更新状态
        transition(action);
    }

    private void transition(String action) {
        if (state instanceof PendingPaymentState) {
            if ("支付".equals(action)) {
                ((PendingPaymentState) state).pay(this);
            } else if ("取消".equals(action)) {
                ((PendingPaymentState) state).cancel(this);
            }
        }
        // ... 其他状态的处理
    }

    public void notifyStateChanged(String message) {
        logger.info("订单 {} 状态变更: {}", orderId, message);
        // 发送通知:短信、推送等
    }
}

客户端使用

OrderContext order = new OrderContext("ORDER-001");

order.execute("支付");    // 待支付 -> 待发货
order.execute("发货");    // 待发货 -> 已发货
order.execute("确认收货"); // 已发货 -> 待收货

System.out.println(order.getState().getStateName());  // 待收货

状态模式 vs 策略模式

状态模式和策略模式结构几乎一样,但解决的问题不同:

维度状态模式策略模式
目的状态决定行为,状态间可转换算法可切换,策略间无关联
切换方式自动切换(通过 context)手动切换(外部注入)
状态关系状态之间有转换关系策略之间无关联
Context 角色持有状态,状态操作 context持有策略,context 不被修改
典型场景订单流转、审批流程支付算法、排序算法
flowchart LR
    subgraph 状态模式
        C[Context] -->|状态切换| S1[StateA]
        S1 -->|转换| S2[StateB]
        S2 -->|转换| S3[StateC]
    end

    subgraph 策略模式
        C2[Context] -->|手动设置| P1[StrategyA]
        C2 -->|手动设置| P2[StrategyB]
    end

有限状态机:StateMachine

状态模式的一个扩展是有限状态机(FSM)。在复杂业务中,可以使用状态机框架来管理状态转换。

状态机核心概念

flowchart LR
    S[State] -->|Event| T[Transition] --> S2[State]
    G[Guard] -.-> T
    A[Action] -.-> T
  • State(状态):系统处于的稳定状态
  • Event(事件):触发状态转换的事件
  • Transition(转换):从源状态到目标状态的转换
  • Guard(守卫):决定转换是否执行的条件
  • Action(动作):转换时执行的动作

使用 Spring StateMachine

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>4.0.0</version>
</dependency>
// 定义状态枚举
public enum OrderState {
    PENDING_PAYMENT,  // 待支付
    PENDING_SHIPMENT, // 待发货
    SHIPPED,         // 已发货
    DELIVERED,       // 已收货
    CANCELLED        // 已取消
}

// 定义事件枚举
public enum OrderEvent {
    PAY,             // 支付
    SHIP,            // 发货
    CONFIRM_RECEIVE, // 确认收货
    CANCEL           // 取消
}
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig
        extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config)
            throws Exception {
        config
            .withConfiguration()
            .autoStartup(false)
            .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states)
            throws Exception {
        states
            .withStates()
            .initial(OrderState.PENDING_PAYMENT)
            .states(EnumSet.allOf(OrderState.class))
            .end(OrderState.CANCELLED)
            .end(OrderState.DELIVERED);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
            throws Exception {
        transitions
            // 待支付 -> 待发货(支付)
            .withExternal()
            .source(OrderState.PENDING_PAYMENT)
            .target(OrderState.PENDING_SHIPMENT)
            .event(OrderEvent.PAY)
            .guard(orderGuard())
            .action(paymentAction())
            // 待支付 -> 已取消(取消)
            .and()
            .withExternal()
            .source(OrderState.PENDING_PAYMENT)
            .target(OrderState.CANCELLED)
            .event(OrderEvent.CANCEL)
            // 待发货 -> 已发货(发货)
            .and()
            .withExternal()
            .source(OrderState.PENDING_SHIPMENT)
            .target(OrderState.SHIPPED)
            .event(OrderEvent.SHIP)
            // 已发货 -> 已收货(确认收货)
            .and()
            .withExternal()
            .source(OrderState.SHIPPED)
            .target(OrderState.DELIVERED)
            .event(OrderEvent.CONFIRM_RECEIVE);
    }

    @Bean
    public StateMachineListener<OrderState, OrderEvent> listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<OrderState, OrderEvent> from,
                                     State<OrderState, OrderEvent> to) {
                log.info("状态变更: {} -> {}",
                    from != null ? from.getId() : "无",
                    to.getId());
            }
        };
    }

    @Bean
    public StateMachineGuard<OrderState, OrderEvent> orderGuard() {
        return context -> {
            // 守卫条件:检查支付金额
            Integer amount = context.getExtendedState()
                .get("amount", Integer.class);
            return amount != null && amount > 0;
        };
    }

    @Bean
    public StateMachineAction<OrderState, OrderEvent> paymentAction() {
        return context -> {
            // 支付成功后的动作:扣库存、发消息等
            log.info("执行支付后动作");
        };
    }
}

使用状态机

@Service
public class OrderService {
    @Autowired
    private StateMachine<OrderState, OrderEvent> stateMachine;

    public void payOrder(String orderId, BigDecimal amount) {
        // 设置上下文数据
        ExtendedState extendedState = stateMachine.getExtendedState();
        extendedState.getVariables().put("orderId", orderId);
        extendedState.getVariables().put("amount", amount);

        // 发送事件
        boolean success = stateMachine.sendEvent(OrderEvent.PAY);

        if (success) {
            log.info("订单支付成功,当前状态: {}",
                stateMachine.getState().getId());
        } else {
            log.error("订单支付失败,当前状态: {}",
                stateMachine.getState().getId());
        }
    }
}

状态模式 vs 责任链模式

两者都可以处理多种情况,但适用场景不同:

维度状态模式责任链模式
处理方式当前状态决定处理逻辑链式传递,找到能处理的处理器
状态转换状态间有转换关系Handler 之间无关联
选择机制状态自动决定下一个状态顺序遍历,处理器决定是否处理
典型场景订单流程、审批流程过滤器链、拦截器链
选择建议
  • 如果业务核心是「状态流转」,使用状态模式
  • 如果业务核心是「请求传递」,使用责任链模式

状态模式的优缺点

优点

  1. 消除 if-else:状态转换逻辑集中管理
  2. 开闭原则:新增状态不需要修改现有代码
  3. 简化 Context:Context 代码更简洁
  4. 状态转换安全:状态转换规则统一管理,避免非法转换
  5. 易于测试:每个状态可以独立测试

缺点

  1. 类数量增加:每个状态都需要一个具体类
  2. 状态机复杂度:状态和转换多时,管理成本增加
  3. 状态间耦合:如果状态之间有共享逻辑,需要通过 Context 提取
状态模式 vs 简单 if-else

状态模式不是银弹。只有当状态转换规则足够复杂时,才需要引入状态模式。

状态数量转换规则复杂度推荐方案
<= 3简单if-else
3-10中等状态模式
> 10复杂状态机框架

思考题

问题 1:如何实现状态的持久化(如订单状态保存到数据库)?

参考答案

几种实现方式:

  1. 状态枚举存储:状态转换后,将枚举值存入数据库
  2. 状态快照:使用备忘录模式保存状态快照
  3. 状态机持久化:Spring StateMachine 支持将状态机状态持久化到 Redis
// 方式1:状态枚举存储
public enum OrderState {
    PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, CANCELLED
}

// 订单实体
@Entity
public class Order {
    @Enumerated(EnumType.STRING)
    private OrderState state;
}

// 加载时恢复状态
public OrderContext loadOrder(String orderId) {
    Order order = orderRepository.findById(orderId);
    OrderContext context = new OrderContext(orderId);
    context.setState(stateMap.get(order.getState()));
    return context;
}

问题 2:如何处理并发状态转换(如两个操作同时修改订单状态)?

参考答案

几种解决方案:

  1. 乐观锁:使用版本号控制
@Version
private Long version;

// 更新时检查版本
@Transactional
public void updateState(Order order, OrderState newState, Long expectedVersion) {
    if (!order.getVersion().equals(expectedVersion)) {
        throw new OptimisticLockException("订单已被其他操作修改");
    }
    order.setState(newState);
    order.setVersion(expectedVersion + 1);
}
  1. 悲观锁:使用数据库行锁
@Transactional
public void updateState(Long orderId, OrderState newState) {
    Order order = orderRepository.findByIdWithLock(orderId);
    // 更新逻辑
}
  1. 状态机内置支持:Spring StateMachine 支持乐观锁
config.withConfiguration()
    .machineType(UuidStateMachineIdResolver)
    .persistence(persistenceSink);

问题 3:状态模式和观察者模式可以一起使用吗?

参考答案

可以,两种模式天然互补:

  • 状态模式:管理状态转换
  • 观察者模式:当状态变化时通知相关方
public class OrderContext {
    private OrderState state;
    private final List<OrderObserver> observers = new CopyOnWriteArrayList<>();

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void setState(OrderState newState) {
        OrderState oldState = this.state;
        this.state = newState;

        // 通知观察者
        for (OrderObserver observer : observers) {
            observer.onStateChanged(this, oldState, newState);
        }
    }
}

public interface OrderObserver {
    void onStateChanged(OrderContext order,
                        OrderState oldState,
                        OrderState newState);
}

// 实现类:库存观察者、消息观察者、审计日志观察者

典型的应用场景:订单状态变更时,通知库存系统扣减库存、通知用户发送推送、记录审计日志