状态迁移表:一种简洁的 FSM 设计工具

发布时间 2023-03-22 21:09:06作者: 小霖2012

关于订单状态迁移表的概述

状态迁移表(State Transition Table)是一种常见的状态机模型表示方式,用于记录状态机中不同状态之间的转移条件。它通常是一个二维表格,其中表格的行表示状态机中的状态,而表格的列则表示可以触发状态转移的事件或条件。表格中的每个单元格记录了在某个状态下,如果满足某个事件或条件,状态机应该转移到哪个状态。因此,状态迁移表可以很好地描述状态机的状态转移规则,便于我们理解和设计状态机。

状态迁移表通常由以下几个部分组成:

状态列表:状态迁移表中需要列出系统中所有可能的状态,每个状态都有一个唯一的名称或编号来表示。在表格中,状态列表通常是位于表格的第一行和第一列的。

事件列表:状态转移通常是由事件触发的,事件列表列出了所有可能的事件,它们可以触发状态之间的转移。在表格中,事件列表通常是位于表格的第一行的。

状态转移表:状态转移表是状态迁移表的核心部分,它描述了系统中各个状态之间的转移关系。在表格中,状态转移表通常是状态列表和事件列表之间的方格。

动作列表:状态转移过程中可以执行一些动作,比如输出日志、更新数据等等。动作列表列出了可能的动作。在表格中,动作列表通常是位于表格的最后一列的。

下面是一个简单的状态迁移表示例:

状态/事件 拨号(Dial) 挂机(Hangup)
空闲 拨号中 空闲
拨号中 - 已连接
已连接 拨号中 空闲

在这个例子中,我们描述了一个简单的电话状态机,包含了三个状态:空闲、拨号中、已连接。其中,事件是拨号和挂机,通过事件触发状态之间的转移。

饿了么的订单状态设计

在我了解饿了么的交易系统的订单状态管理就是通过解释状态迁移表来实现的。具体可以参考 https://zhuanlan.zhihu.com/p/83388356

饿了么订单系统中,订单状态的设计相对复杂,主要是为了支持多种业务场景和灵活的状态流转。
饿了么订单系统中,订单状态被分为两类:基础状态和高级状态。基础状态包括:创建、待支付、已支付、待发货、已发货、已确认、已完成、已取消。其中,已确认是一个临时状态,表示订单已被商家确认,但尚未发货。高级状态是在基础状态的基础上,进一步细分出来的状态,主要用于支持特定的业务场景。例如,对于团购订单,还需要增加"拼团中"、"拼团失败"、"拼团成功"等状态。对于预约订单,还需要增加"待预约"、"预约成功"、"预约失败"等状态。

饿了么订单系统中,订单状态的流转是通过一张状态图来控制的。状态图中,每个节点表示一个状态,每个箭头表示状态之间的流转。订单状态流转过程中,会触发不同的事件。例如,当订单从"待支付"状态流转到"已支付"状态时,会触发支付事件;当订单从"已发货"状态流转到"已确认"状态时,会触发确认事件。对于不同的状态,会有不同的操作权限。例如,只有商家才能对"待发货"状态的订单进行发货操作;只有用户才能对"已完成"状态的订单进行评价操作等。
总之,饿了么订单系统中,订单状态的设计考虑了多种业务场景和灵活的状态流转,并且通过一张状态图来控制状态的流转,从而实现了高效、稳定、灵活的订单管理。

以下是一个简单的示例状态迁移表,用于描述订单状态FSM中不同状态之间的转移条件:

当前状态 / 触发事件 订单支付 订单发货 订单确认 订单完成
订单创建 订单待支付 - - -
订单待支付 订单已支付 - - -
订单已支付 订单待发货 - - -
订单待发货 - 订单已发货 - -
订单已发货 - - 订单确认 -
订单确认 - - - 交易完成

在上面的状态迁移表中,表格的行表示订单状态FSM中的不同状态,表格的列则表示不同的事件或条件。例如,第一列表示当订单状态为“订单创建”时,如果触发了“订单支付”事件,状态机应该转移到“订单待支付”状态。而对于某些状态和事件组合,状态机不允许进行状态转移,这在状态迁移表中用“-”表示。
注意,状态迁移表只是一种常见的状态机模型表示方式,它并不是唯一的方式。在实际应用中,我们可以根据具体的需求和场景选择不同的状态机模型来表示状态转移规则。

表结构设计

解释迁移表的方式是记录订单的驱动状态,以及流转下一个节点的推动状态,也是一个不错的设计方式,关键是好维护,合适才是最好的设计。

一般交易系统的订单状态主要包含两部分,支付状态和发货的状态


public enum OrderStatus {
    CREATED, // 订单创建
    UNPAID, // 订单待支付
    PAID, // 订单已支付
    TO_BE_SHIPPED, // 订单待发货
    SHIPPED, // 订单已发货
    CONFIRMED, // 订单确认
    COMPLETED, // 交易完成
    CANCELED // 订单已取消
}

public enum OrderEvent {
    PAY, // 支付
    SHIP, // 发货
    RECEIVE, // 确认收货
    CANCEL // 取消订单
}

订单状态解释迁移表的实现

订单状态机实现
以上我们已经定义好了订单状态迁移表,下面我们就可以通过代码实现订单状态机了。

我们需要实现以下几个类:

OrderStatus:订单状态枚举类。
OrderEvent:订单事件枚举类。
OrderStateMachine:订单状态机类,封装了状态迁移逻辑。
Order:订单类,具有状态属性,并且能够执行状态变更操作。

当我们使用状态迁移表来描述状态机时,实现的方式可能有多种。然后,我们定义一个类来表示订单状态机,它包含了状态转移的逻辑:

public class OrderStateMachine {
    private static final Map<OrderStatus, Map<OrderEvent, OrderStatus>> stateMachine = new HashMap<>();
    static {
        // 定义状态转移表
        Map<OrderEvent, OrderStatus> createdTransitions = new EnumMap<>(OrderEvent.class);
        createdTransitions.put(OrderEvent.PAY, OrderStatus.UNPAID);
        createdTransitions.put(OrderEvent.CANCEL, OrderStatus.CANCELED);
        stateMachine.put(OrderStatus.CREATED, createdTransitions);

        Map<OrderEvent, OrderStatus> unpaidTransitions = new EnumMap<>(OrderEvent.class);
        unpaidTransitions.put(OrderEvent.PAY, OrderStatus.PAID);
        unpaidTransitions.put(OrderEvent.CANCEL, OrderStatus.CANCELED);
        stateMachine.put(OrderStatus.UNPAID, unpaidTransitions);

        Map<OrderEvent, OrderStatus> paidTransitions = new EnumMap<>(OrderEvent.class);
        paidTransitions.put(OrderEvent.SHIP, OrderStatus.TO_BE_SHIPPED);
        paidTransitions.put(OrderEvent.CANCEL, OrderStatus.CANCELED);
        stateMachine.put(OrderStatus.PAID, paidTransitions);

        Map<OrderEvent, OrderStatus> toBeShippedTransitions = new EnumMap<>(OrderEvent.class);
        toBeShippedTransitions.put(OrderEvent.RECEIVE, OrderStatus.CONFIRMED);
        toBeShippedTransitions.put(OrderEvent.CANCEL, OrderStatus.CANCELED);
        stateMachine.put(OrderStatus.TO_BE_SHIPPED, toBeShippedTransitions);

        Map<OrderEvent, OrderStatus> shippedTransitions = new EnumMap<>(OrderEvent.class);
        shippedTransitions.put(OrderEvent.RECEIVE, OrderStatus.CONFIRMED);
        shippedTransitions.put(OrderEvent.CANCEL, OrderStatus.CANCELED);
        stateMachine.put(OrderStatus.SHIPPED, shippedTransitions);

        Map<OrderEvent, OrderStatus> confirmedTransitions = new EnumMap<>(OrderEvent.class);
        confirmedTransitions.put(OrderEvent.CANCEL, OrderStatus.COMPLETED);
        stateMachine.put(OrderStatus.CONFIRMED, confirmedTransitions);
    }

    public static OrderStatus nextState(OrderStatus currentStatus, OrderEvent event) {
        Map<OrderEvent, OrderStatus> transitions = stateMachine.get(currentStatus);
        if (transitions == null) {
            throw new IllegalStateException("Invalid order status: " + currentStatus);
        }
        OrderStatus nextStatus = transitions.get(event);
        if (nextStatus == null) {
            throw new IllegalStateException("Invalid event " + event + " for order status " + currentStatus);
        }
        return nextStatus;
    }
}

假设我们有一个订单 order,它的状态为 ORDER_CREATED,接下来我们需要将它流转到 ORDER_PAID 状态,那么可以按照以下流程:

  • 判断订单当前状态是否为 ORDER_CREATED
  • 如果是,则将订单状态设置为 ORDER_TO_BE_PAID,并且更新订单的 update_time,同时添加一条状态变更记录到订单状态表中,记录的事件为 PAY_ORDER,表示用户支付订单
  • 进入支付流程,等待用户支付
  • 用户支付成功后,将订单状态设置为 ORDER_PAID,并且更新订单的 update_time,同时添加一条状态变更记录到订单状态表中,记录的事件为 ORDER_PAID,表示订单已支付
if (order.getOrderStatus() == OrderStatus.ORDER_CREATED) {
    order.setOrderStatus(OrderStatus.ORDER_TO_BE_PAID);
    order.setUpdateTime(new Date());
    orderStatusService.createOrderStatus(order.getOrderId(), OrderStatusEvent.PAY_ORDER, OrderStatus.ORDER_TO_BE_PAID);
    
    // 进入支付流程等待用户支付
    // ...
    
    order.setOrderStatus(OrderStatus.ORDER_PAID);
    order.setUpdateTime(new Date());
    orderStatusService.createOrderStatus(order.getOrderId(), OrderStatusEvent.ORDER_PAID, OrderStatus.ORDER_PAID);
}