状态机:从传统的 if-else 到高效的状态管理

前言

在软件开发中,状态管理是一个常见且重要的任务。在实现状态流转时,往往会依赖于传统的if-else语句来控制状态的流转。然而,随着项目的复杂性增加,使用if-else语句会带来一系列痛点:

  1. 可读性差:嵌套的if-else语句使得代码难以阅读和理解。
  2. 维护困难:状态流转逻辑分散在多个地方,修改时容易引入错误。
  3. 扩展性差:添加新状态或状态流转时,往往需要修改大量代码。

为了解决这些问题,状态机应运而生。

状态机简介

状态机(State Machine)是一种数学模型,用于描述系统状态以及在这些状态之间的流转和动作等行为。一般情况下,我们讨论的状态机的状态个数是有限的,即有限状态机(Finite State Machine, FSM)。状态机有以下几个核心概念:

  • 状态(State):系统可能处于的不同状态,如开、关等。
  • 事件(Event):导致状态流转的触发事件,如按钮点击。
  • 动作(Action):事件发生之后要执行的动作。
  • 流转(Transition):从一个状态到另一个状态的过渡。

状态机的核心思想是将复杂的状态管理逻辑抽象为一个清晰的模型,使得状态流转更加直观和易于维护。

状态机对比

特性Spring StateMachineCOLA StateMachine重量级较重,如果不持久化状态机,需要为每个请求创建新的状态机实例,对内存压力大轻量级,无状态设计,单例实例处理所有请求,性能高线程安全有状态,需要额外处理以确保线程安全,如使用锁或 ThreadLocal天生线程安全,因为是无状态的上手难度较高,学习曲线陡峭较低,易于理解和使用功能丰富度功能丰富,支持复杂状态机模型,如状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等功能相对简单,专注于状态流转异常处理异常可能被状态机内部消化,导致事务不能正常回滚能够自然抛出异常社区与文档社区支持较好,文档齐全社区支持一般,文档相对较少适用场景适用于需要复杂状态管理的场景适用于业务逻辑简单,需要快速实现的场景

对于一般的不是特别复杂的业务场景,建议使用 COLA StateMachine,下文将着重介绍 COLA StateMachine。

COLA StateMachine

COLA StateMachine 核心概念

  • State:状态
  • Event:事件,状态由事件触发,引起变化
  • Transition:流转,表示从一个状态到另一个状态
    • External Transition:外部流转,两个不同状态之间的流转
    • Internal Transition:内部流转,同一个状态之间的流转
  • Condition:条件,表示是否允许到达某个状态(非必需)
  • Action:动作,到达某个状态之后,可以做什么

COLA StateMachine 示例

以完成上图所示状态流转为例:

状态(State):待支付、待发货、已取消、配送中、待收货、已完成。

public enum OrderState {
    PENDING_PAYMENT(10, "待支付"),
    PENDING_DELIVERY(20, "待发货"),
    IN_DELIVERY(30, "配送中"),
    PENDING_RECEIVE(40, "待收货"),
    COMPLETED(50, "已完成"),
    CANCELLED(60, "已取消");
}

事件(Event):完成支付、取消订单、商家发货、到达新站点、配送完成、确认收货。

public enum OrderEvent {
    PAYMENT_COMPLETED("完成支付"),
    CANCEL("取消订单"),
    DELIVERY("商家发货"),
    ARRIVE_NEW_SITE("到达新站点"),
    DELIVERY_COMPLETED("配送完成"),
    RECEIVE_CONFIRMED("确认收货");
}

流转(Transition)

  • 外部流转(External Transition):待支付-->待发货、待支付-->已取消、待发货-->已取消、待发货-->配送中、配送中-->待收货、待收货-->已完成。
  • 内部流转(Internal Transition):配送中-->配送中。
@Configuration
public class OrderStateMachineConfig {

    @Resource
    private OrderAction orderAction;

    @Bean("orderStateMachine")
    public StateMachine<OrderState, OrderEvent, OrderContext> orderStateMachine() {
        StateMachineBuilder<OrderState, OrderEvent, OrderContext> builder = StateMachineBuilderFactory.create();
        // 待支付->待发货
        builder.externalTransition()
            .from(OrderState.PENDING_PAYMENT)
            .to(OrderState.PENDING_DELIVERY)
            .on(OrderEvent.PAYMENT_COMPLETED)
            .when(checkCondition())
            .perform(orderAction);

        // 待支付/待发货->已取消
        builder.externalTransitions()
            .fromAmong(OrderState.PENDING_PAYMENT, OrderState.PENDING_DELIVERY)
            .to(OrderState.CANCELLED)
            .on(OrderEvent.CANCEL)
            .perform(orderAction);

        // 待发货->配送中
        builder.externalTransition()
            .from(OrderState.PENDING_DELIVERY)
            .to(OrderState.IN_DELIVERY)
            .on(OrderEvent.DELIVERY)
            .perform(orderAction);

        // 配送中->配送中
        builder.internalTransition()
            .within(OrderState.IN_DELIVERY)
            .on(OrderEvent.ARRIVE_NEW_SITE)
            .perform(orderAction);

        // 配送中->待收货
        builder.externalTransition()
            .from(OrderState.IN_DELIVERY)
            .to(OrderState.PENDING_RECEIVE)
            .on(OrderEvent.DELIVERY_COMPLETED)
            .perform(orderAction);

        // 待收货->已完成
        builder.externalTransition()
            .from(OrderState.PENDING_RECEIVE)
            .to(OrderState.COMPLETED)
            .on(OrderEvent.RECEIVE_CONFIRMED)
            .perform(orderAction);
        return builder.build(StateMachineEnum.ORDER_STATE_MACHINE.getMachineId());
    }
}

条件(Condition):checkCondition()

private Condition<OrderContext> checkCondition() {
     return (ctx) -> true;
}

动作(Action):OrderAction

@Component
public class OrderAction implements Action<OrderState, OrderEvent, OrderContext> {

    @Override
    public void execute(OrderState from, OrderState to, OrderEvent event, OrderContext context) {
        // do biz
    }
}

我们可以在 checkCondition() 方法中检验条件是否满足,例如校验必填参数等;可以在 OrderAction 类的 execute() 方法中实现自己的业务逻辑,例如变更订单状态等。当然,每个流转过程可以自定义Condition和Action,不必全部一致。

看起来万事俱备了,那么如何发布事件呢?

发布事件

public OrderState fireEvent(OrderEvent orderEvent, OrderContext orderContext) {
   OrderEntity orderEntity = orderGateway.getByOrderNo(orderContext.getOrderNo());
   OrderState sourceState = OrderState.getByCode(orderEntity.getOrderStatus());
   return orderStateMachine.fireEvent(sourceState, orderEvent, orderContext);
}

有的人可能会想到,万一当前订单状态为待支付,却发布了一个不匹配的事件,比如配送完成,这时候会则怎样呢?

其实在状态机构建时可以设置FailCallback参数进行自定义处理,如果不设置,默认为 NumbFailCallback,不进行任何处理。

public interface StateMachineBuilder<S, E, C> {
     void setFailCallback(FailCallback<S, E, C> callback);
}

public class NumbFailCallback<S, E, C> implements FailCallback<S, E, C> {
    @Override
    public void onFail(S sourceState, E event, C context) {
        // do nothing
    }
}

总结

尽管if-else语句在简单场景下可行,但随着业务逻辑的复杂化,其可读性、可维护性和扩展性的问题逐渐显现。相对而言,状态机以其清晰的模型和直观的流转逻辑,提供了一种更为高效和系统化的状态管理方法。通过引入状态机,我们将状态流转逻辑抽象化,不仅使得代码更加易于理解和维护,还提升了系统的可扩展性。

参考资料

  1. https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html
  2. https://blog.csdn.net/significantfrank/article/details/104996419
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值