系统设计中的有限状态机FSM技术解析

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

FSM(有限状态机)是一种数学模型,用于描述系统或程序的行为,SM的状态机设计要点包括确定状态集合、转移函数、初始状态和结束状态,绘制状态图和转移表格,以及状态机的实现。其设计要点包括以下几个方面:

  • 确定状态集合:需要明确系统可能的所有状态,并将其表示为一个状态集合。状态集合应该包含系统的所有可能状态。

  • 确定转移函数:需要确定系统从一个状态到另一个状态的转移函数。这可以通过定义每个状态之间的转移条件和对应的动作来实现。转移条件可以是输入信号、计时器到期等等。

  • 确定初始状态:需要指定系统的初始状态。通常情况下,系统的初始状态是指系统在运行前处于的状态。

  • 确定结束状态:需要指定系统的结束状态。结束状态是指系统在某些条件下停止的状态,通常情况下,结束状态是指系统达到一种特定的状态。

  • 状态机的实现:需要将状态机转换为程序代码,以便于在计算机上运行和控制。在实现时需要考虑状态机的可重入性、可移植性、可靠性等因素。

一般交易系统的订单状态可能包含

public enum OrderState {
    CREATED("订单创建"),
    WAIT_PAYMENT("订单待支付"),
    PAID("订单已支付"),
    WAIT_DELIVER("订单待发货"),
    DELIVERED("订单已发货"),
    CONFIRMED("订单确认"),
    COMPLETED("交易完成");

    private String desc; // 订单状态描述

    OrderState(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

一个最简的FSM实现状态流转的实现逻辑,主要包含的内容是状态集合的管理、状态转移函数、状态添加函数、以及状态流转函数

在这个实现中,我们使用了Java 8引入的函数式接口Consumer,用于执行每个状态的回调函数。状态转移函数使用了Map<String, Map<String, String>>结构,其中外层Map的Key表示起始状态,内层Map的Key表示转移条件,Value表示目标状态。状态转移过程使用了一个循环,在每次循环中执行当前状态的回调函数,判断当前状态的转移函数是否存在并且是否满足转移条件,如果满足就执行状态转移,直到结束状态或者没有对应的转移函数为止。具体的转移条件需要根据具体的实现进行修改。

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

public class OrderStateMachine {
    private Map<OrderState, Consumer<Order>> states = new HashMap<>(); // 状态集合
    private Map<OrderState, Map<OrderState, OrderCondition>> transitions = new HashMap<>(); // 转移函数

    public OrderStateMachine() {
        // 添加状态和状态回调函数
        addState(OrderState.CREATED, order -> System.out.println("订单创建"));
        addState(OrderState.WAIT_PAYMENT, order -> System.out.println("订单待支付"));
        addState(OrderState.PAID, order -> System.out.println("订单已支付"));
        addState(OrderState.WAIT_DELIVER, order -> System.out.println("订单待发货"));
        addState(OrderState.DELIVERED, order -> System.out.println("订单已发货"));
        addState(OrderState.CONFIRMED, order -> System.out.println("订单确认"));
        addState(OrderState.COMPLETED, order -> System.out.println("交易完成"));

        // 添加状态之间的转移条件和动作
        addTransition(OrderState.CREATED, OrderState.WAIT_PAYMENT, order -> order.status.equals(OrderState.WAIT_PAYMENT));
        addTransition(OrderState.WAIT_PAYMENT, OrderState.PAID, order -> order.status.equals(OrderState.PAID));
        addTransition(OrderState.PAID, OrderState.WAIT_DELIVER, order -> order.status.equals(OrderState.WAIT_DELIVER));
        addTransition(OrderState.WAIT_DELIVER, OrderState.DELIVERED, order -> order.status.equals(OrderState.DELIVERED));
        addTransition(OrderState.DELIVERED, OrderState.CONFIRMED, order -> order.status.equals(OrderState.CONFIRMED));
        addTransition(OrderState.CONFIRMED, OrderState.COMPLETED, order -> order.status.equals(OrderState.COMPLETED));
    }

    public void addState(OrderState state, Consumer<Order> callback) {
        states.put(state, callback); // 添加状态到状态集合,callback是进入该状态时需要执行的回调函数
    }

    public void addTransition(OrderState fromState, OrderState toState, OrderCondition condition) {
        if (!transitions.containsKey(fromState)) {
            transitions.put(fromState, new HashMap<>()); // 如果转移函数中不存在fromState状态,需要先创建一个Map
        }
        transitions.get(fromState).put(toState, condition); // 添加状态之间的转移条件和动作
    }

    public void run(Order order, OrderState initialState) {
        OrderState currentState = initialState; // 设置初始状态
        while (currentState != null && states.containsKey(currentState)) {
            Consumer<Order> callback = states.get(currentState); // 执行当前状态的回调函数
            if (callback != null) {
                callback.accept(order);
            }
            Map<OrderState, OrderCondition> currentTransitions = transitions.get(currentState); // 获取当前状态的转移函数
            if (currentTransitions != null) {
                for (Map.Entry<OrderState, OrderCondition> transition : currentTransitions.entrySet()) {
                    OrderCondition condition = transition.getValue();
                    if (condition.check(order)) { // 判断转移条件是否满足
                        currentState = transition.getKey(); // 执行状态转移
                        break;
                    }
                }
            } else {
                currentState = null; // 当前状态没有对应的转移函数,跳出循环
           

在实际运行中,我们可以根据工程的业务代码,对状态机进行编排

public class OrderService {
    public void processOrder(Order order) {
        OrderStateMachine stateMachine = new OrderStateMachine();
        stateMachine.run(order, OrderState.CREATED); // 从订单创建状态开始执行
    }
}

本人在具体工程的实现,实现逻辑跟上面的代码示例差不多,只是因为我是工程化的实现逻辑。在所经历的代码中,订单状态是散落是各个服务类接口,状态的一致性也比较难以维护,比如有的用version版本有的用分布式锁。导致订单状态的前置校验也没有收拢,其实是比较难以维护的。交易逆向的状态比正向要复杂很多,后面做了一定的重构,改成了FSM的状态机模型。当然实现的过程中还需要考虑并发场景的处理。