基于配置文件的状态机设计(学习心得)

概念

状态机是一种用于描述系统或对象在不同状态之间转换行为的数学模型和控制机制,它通过定义状态、事件、转换规则及对应动作,来刻画一个实体在其生命周期中对事件的响应过程。其核心在于管理"状态"的流转,而非简单的流程执行,因此特别适用于那些状态变化复杂但模式清晰的场景。

为什么需要状态机

状态机的核心价值在于‌用清晰的结构管理复杂的状态流转逻辑,避免代码失控。当系统存在多个状态、频繁的状态切换和复杂的触发条件时,传统的 if-else 或嵌套判断会迅速演变为难以维护的"面条代码",而状态机通过抽象出状态、事件、转换规则,让逻辑变得可读、可维护、可扩展。

来看这样一段代码,他需要实现订单状态的变化,同时要拒绝非法的状态变化情况。

java 复制代码
        
public String paidOrder(String orderId, StateEnum state) {
    String message = "";
    if (state.equals(StateEnum.PENDING)) {
        message = "订单支付成功";
    } else {
        message = "当前订单状态不支持支付操作";
    }
    return message;
}

@Override
public String shipOrder(String orderId, StateEnum state) {
    String message = "";
    if (state.equals(StateEnum.PAID)) {
        message = "订单发货成功";
    } else {
        message = "当前订单状态不支持发货操作";
    }
    return message;
}

@Override
public String receiveOrder(String orderId, StateEnum state) {
    String message = "";
    if (state.equals(StateEnum.SHIPPED)) {
        message = "订单收货成功";
    } else {
        message = "当前订单状态不支持收货操作";
    }
    return message;
}

@Override
public String cancelOrder(String orderId, StateEnum state) {
    String message = "";
    if (state.equals(StateEnum.PENDING)) {
        message = "支付中状态下订单取消成功";
    } else if (state.equals(StateEnum.PAID)) {
        message = "支付结束状态下订单取消成功";
    } else if(state.equals(StateEnum.SHIPPED)){
        message = "运货状态下订单取消成功订单取消成功";
    }else if( state.equals(StateEnum.RECEIVED)){
        message = "已收货状态下订单取消成功";
    } else {
        message = "当前订单状态不支持取消操作";
    }
    return message;
}

以上的代码是模拟电商订单状态转换写的一段service层的代码,很显然,这样子的设计存在许多不合理之处:

1.随着状态规则和状态的逐渐增加,if-else会越来越多,并且有时候可能新增一个规则或者状态需要修改多个方法或者类文件。

2.这样子的代码并没有复用性,因为很多规则在多个类或者方法里重复出现,并且要执行的if-else语句块也可能在多个类或者方法里重复出现。

3.开发人员去维护和更新这么一段极其多if-else的代码很容易遗忘边界条件,导致程序报错。并且由于代码的可读性十分差也让寻找bug变得非常困难。

4.我个人认为这样的设计并不符合单一职责的原则,状态机的变更我觉得应该属于一个职责,但是多个类和方法都共同维护和承担这这个职责。并且开发人员每次开发功能都需要去熟记规则和小心的去维护他。

核心组成要素

状态(State)

表示系统在某一时刻所处的条件或状况,例如订单的"已下单""已支付""已发货"等。

事件(Event)

触发状态转换的外部或内部动作,如用户点击按钮、消息到达、定时器触发等。

转换(Transition)

定义了在特定事件发生且满足条件时,系统从一个状态转移到另一个状态的路径。

动作(Action)

决定某个转换是否可以执行的逻辑判断,也称为"守卫(Guard)"。

条件(Condition)

在状态进入、退出或转换过程中执行的具体操作,如记录日志、发送通知等。

实现

实现细节

了解了状态机的基本实现逻辑之后我试着自己实现一个可通用的状态机,但是我并没有实现它里面条件逻辑。他可以通过配置文件,仅通过三行就创建了一个适合自己业务的状态机,当然里面具体的动作类和状态类需要开发者自己编写。

我是这么设计的,首先需要设计一个状态机的配置类,这样子可以让状态机通过获取实例的方法根据配置类动态的创建出来。状态机配置类通常包括状态规则、以及所有的事件枚举和状态枚举。它支持代码编写和配置文件进行初始化,其中配置文件的初始化我是通过反射去实现的。

然后是状态规则类,状态规则类里有当前状态,下一个状态,触发事件以及执行动作四个变量。其中执行动作是一个继承了MachineBaseAction 接口的类,由开发者自己去实现。他里面只有一个invoke方法,参数类型是动作请求类,返回类型是动作响应类。

最后是状态机类的设计,他的构造方法是私有的,只允许传入配置类对象进行实例构造,这样子我觉得可以让开发者不需要关注内部实现去创建一个适合自己业务的状态机。其中他的核心方法是excute 方法,它遍历所有规则去找匹配的一项,并执行他们的动作执行类中的invoke方法,返回值的类型是动作响应类。

UML类图

状态机配置类

java 复制代码
package org.example.statemachine;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class StateMachineConfig<S extends Enum<S>, E extends Enum<E>> {
    private List<StateTrasaction<S, E>> stateTrasactionList;
    private Map<String,E> eventMap;
    private Map<String,S> StateMap;

    // 设置状态转换
    public StateMachineConfig<S, E> stateTrasaction(List<StateTrasaction<S, E>> stateTrasactionList) {
        if (stateTrasactionList != null) {
            this.stateTrasactionList = new ArrayList<>(stateTrasactionList); // 浅拷贝
        } else {
            this.stateTrasactionList = new ArrayList<>();
        }
        eventMap=new HashMap<>();
        StateMap=new HashMap<>();
        return this;
    }

    public List<StateTrasaction<S, E>> getStateTrasactionList() {
        return stateTrasactionList;
    }

    public Map<String, E> getEventMap() {
        return eventMap;
    }

    public Map<String, S> getStateMap() {
        return StateMap;
    }

    public StateMachineConfig<S, E> eventMap(Class<E> enumClass) {
        E[] enumConstants = enumClass.getEnumConstants();
        for (E eEnum : enumConstants) {
            this.eventMap.put(eEnum.name(), eEnum);
        }
        return this;
    }
    public StateMachineConfig<S, E> StateMap(Class<S> enumClass) {
        S[] enumConstants = enumClass.getEnumConstants();
        for (S sEnum : enumConstants) {
            this.StateMap.put(sEnum.name(), sEnum);
        }
        return this;
    }

    public static <S extends Enum<S>, E extends Enum<E>> StateMachineConfig<S, E> fromConfigFile(String packName,String filePath, Class<S> stateClass, Class<E> eventClass) {
        try {
            // 读取配置文件内容
            String configFileContent = readConfigFile(filePath);

            // 使用 Jackson ObjectMapper 解析 JSON
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode configJson = objectMapper.readTree(configFileContent);

            // 构建 StateMachineConfig 实例
            StateMachineConfig<S, E> config = new StateMachineConfig<>();

            // 解析状态转换规则
            JsonNode transitions = configJson.get("transitions");
            List<StateTrasaction<S, E>> transactionList = new ArrayList<>();
            if (transitions != null && transitions.isArray()) {
                for (JsonNode transition : transitions) {
                    String sourceStr = transition.get("source").asText();
                    String eventStr = transition.get("event").asText();
                    String targetStr = transition.get("target").asText();
                    String actionClassName = transition.get("action").asText(); // 读取事件类名

                    S source = Enum.valueOf(stateClass, sourceStr);
                    E event = Enum.valueOf(eventClass, eventStr);
                    S target = Enum.valueOf(stateClass, targetStr);

                    // 通过反射初始化事件对象
                    Class<?> actionClass = Class.forName(packName + actionClassName);
                    MachineBaseAction action = (MachineBaseAction) actionClass.getDeclaredConstructor().newInstance();

                    StateTrasaction<S, E> transaction = new StateTrasaction<S, E>()
                            .source(source)
                            .action(action)
                            .target(target)
                            .event(event); // 设置事件对象
                    transactionList.add(transaction);
                }
            }
            config.stateTrasaction(transactionList);

            // 初始化动作和状态映射
            config.eventMap(eventClass).StateMap(stateClass);
            return config;
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse config file: " + filePath, e);
        }
    }

    private static String readConfigFile(String filePath) {
        try {
            return Files.readString(Paths.get(filePath));
        } catch (IOException e) {
            throw new RuntimeException("Failed to read config file: " + filePath, e);
        }
    }

}

状态机类

typescript 复制代码
package org.example.statemachine;

import java.util.List;
import java.util.Map;

public class StateMachine<S extends Enum<S>, E extends Enum<E>> {
    private List<StateTrasaction<S, E>> stateTrasactions;
    private Map<String,E> eventMap;
    private Map<String,S> StateMap;
    private StateMachine() {

    }

    public Map<String, E> getEventMap() {
        return eventMap;
    }
    public Map<String, S> getStateMap() {
        return StateMap;
    }

    public static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> create(StateMachineConfig<S, E> stateMachineConfig) {
        StateMachine<S, E> stateMachine = new StateMachine<>();
        init(stateMachine,stateMachineConfig);
        return stateMachine;
    }

    public static<S extends Enum<S>, E extends Enum<E>> void init(StateMachine<S, E> stateMachine, StateMachineConfig<S,E> stateMachineConfig) {
        stateMachine.setStateTrasactions(stateMachineConfig.getStateTrasactionList())
                    .setEventMap(stateMachineConfig.getEventMap())
                .setStateMap(stateMachineConfig.getStateMap());

    }



    private StateMachine<S,E> setStateTrasactions(List<StateTrasaction<S, E>> stateTrasaction) {
        this.stateTrasactions = stateTrasaction;
        return this;
    }

    private StateMachine<S,E> setEventMap(Map<String,E> eventMap) {
        this.eventMap = eventMap;
        return this;
    }

    private StateMachine<S,E> setStateMap(Map<String,S> stateMap) {
        this.StateMap = stateMap;
        return this;
    }


    public ActionResponseData execute(S state, E event, ActionRequestData<S> requestData) throws Exception {
        for (StateTrasaction<S, E> stateTrasaction : stateTrasactions) {
            if (stateTrasaction.getCurrentState().equals(state) && stateTrasaction.getEvent().equals(event)) {

                return stateTrasaction.getAction().invoke(requestData);
            }
        }
        return new ActionResponseData().setMessage("no such state or event");
    }
}

规则定义类

kotlin 复制代码
package org.example.statemachine;

public class StateTrasaction<S, E> {
    private S currentState;
    private S nextState;
    private E event;
    private MachineBaseAction action;


    public StateTrasaction<S, E> target(S nextState) {
        this.nextState = nextState;
        return this;
    }

    public StateTrasaction<S, E> event(E event) {
        this.event = event;
        return this;
    }

    public StateTrasaction<S, E> action(MachineBaseAction action) {
        this.action = action;
        return this;
    }

    public StateTrasaction<S, E> source(S currentState) {
        this.currentState = currentState;
        return this;
    }

    public  S getCurrentState() {
        return currentState;
    }
    public  S getNextState() {
        return nextState;
    }
    public  E getEvent() {
        return event;
    }
    public MachineBaseAction getAction() {
        return action;
    }
}

动作执行请求类

kotlin 复制代码
package org.example.statemachine;

import java.util.Map;

public class ActionRequestData<S> {
    private Map<String, Object> data;
    private S currentState;
    private S nextState;

    public ActionRequestData(Map<String, Object> data, S currentState, S nextState) {
        this.data = data;
        this.currentState = currentState;
        this.nextState = nextState;
    }

    public ActionRequestData() {

    }

    public Map<String, Object> getData() {
        return data;
    }

    public S getCurrentState() {
        return currentState;
    }

    public S getNextState() {
        return nextState;
    }

    public ActionRequestData setData(Map<String, Object> data) {
        this.data = data;
        return this;
    }

    public ActionRequestData setCurrentState(S currentState) {
        this.currentState = currentState;
        return this;
    }

    public ActionRequestData setNextState(S nextState) {
        this.nextState = nextState;
        return this;
    }
}

动作执行响应类

typescript 复制代码
package org.example.statemachine;

import java.util.Map;

public class ActionResponseData {
    private Map<String, Object> data;
    private String message;


    public ActionResponseData(Map<String, Object> data, String message) {
        this.data = data;
        this.message = message;
    }

    public ActionResponseData() {
    }

    public Map<String, Object> getData() {
        return data;
    }
    public String getMessage() {
        return message;
    }
    public ActionResponseData setData(Map<String, Object> data) {
        this.data = data;
        return this;
    }
    public ActionResponseData setMessage(String message) {
        this.message = message;
        return this;
    }
}

动作执行接口

java 复制代码
package org.example.statemachine;

public interface MachineBaseAction {
    public ActionResponseData invoke(ActionRequestData requestData) throws Exception;
}

配置文件格式

json 复制代码
{
  "transitions": [
    {
      "source": "PENDING",
      "event": "PAY_ORDER",
      "target": "PAID",
      "action": "PendingToPayOrderAction"
    },
    {
      "source": "PAID",
      "event": "SHIP_ORDER",
      "target": "SHIPPED",
      "action": "PaidToShipOrderAction"
    },
    {
      "source": "SHIPPED",
      "event": "RECEIVE_ORDER",
      "target": "RECEIVED",
      "action": "ShippedToReceiveOrderAction"
    },
    {
      "source": "PENDING",
      "event": "CANCEL_ORDER",
      "target": "CANCELLED",
      "action": "PendingToCancelOrderAction"
    },
    {
      "source": "PAID",
      "event": "CANCEL_ORDER",
      "target": "CANCELLED",
      "action": "PaidToCancelOrderAction"
    }
  ]
}

测试

java 复制代码
package org.example;

import org.example.statemachine.*;
import org.example.statemachine.stateenum.EventEnum;
import org.example.statemachine.stateenum.StateEnum;

import java.util.Map;

//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Main {
    static void main() {
        String filePath = "src/main/resources/statemachine.json"; // 示例路径
        // 示例状态转换规则
        //待支付到已支付
        // 定义状态转换规则
        StateMachineConfig<StateEnum, EventEnum> config = StateMachineConfig.fromConfigFile("org.example.statemachine.action.",
                filePath,
                StateEnum.class,
                EventEnum.class
        );

        // 创建状态机实例
        StateMachine<StateEnum, EventEnum> stateMachine = StateMachine.create(config);
        System.out.println("所有状态为"+stateMachine.getStateMap().toString());
        System.out.println("所有事件为"+stateMachine.getEventMap().toString());
        // 测试用例1:待支付 → 已支付
        testTransition(stateMachine, StateEnum.PENDING, EventEnum.PAY_ORDER, "待支付到已支付");

        // 测试用例2:已支付 → 已发货
        testTransition(stateMachine, StateEnum.PAID, EventEnum.SHIP_ORDER, "已支付到已发货");

        // 测试用例3:已发货 → 已收货
        testTransition(stateMachine, StateEnum.SHIPPED, EventEnum.RECEIVE_ORDER, "已发货到已收货");

        // 测试用例4:取消订单(从待支付)
        testTransition(stateMachine, StateEnum.PENDING, EventEnum.CANCEL_ORDER, "取消订单(待支付)");

        // 测试用例5:取消订单(从已支付)
        testTransition(stateMachine, StateEnum.PAID, EventEnum.CANCEL_ORDER, "取消订单(已支付)");

        // 测试用例6:非法操作(已收货状态下支付)
        testTransition(stateMachine, StateEnum.RECEIVED, EventEnum.PAY_ORDER, "非法操作:已收货状态下支付");

        // 测试用例7:非法操作(已取消状态下发货)
        testTransition(stateMachine, StateEnum.CANCELLED, EventEnum.SHIP_ORDER, "非法操作:已取消状态下发货");

    }
    private static void testTransition(StateMachine<StateEnum, EventEnum> stateMachine, StateEnum currentState, EventEnum event, String description) {
        try {
            ActionRequestData<StateEnum> requestData = new ActionRequestData<>();
            requestData.setData(Map.of("testCase", description));
            requestData.setCurrentState(currentState);
            requestData.setNextState(null); // 下一状态由状态机决定

            ActionResponseData response = stateMachine.execute(currentState, event, requestData);
            System.out.println( "描述:"+description+" 执行结果:" + response.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

总结

我觉得对于一些主流的状态机设计理念和方法我还是有许多学习的地方。我是在实习工作开发中遇到了相关的问题,同时也有了解Spring状态机的一些使用,看过一些文章的介绍,就突发奇想能不能自己写一个可以根据配置文件写一个比较通用的状态机,因为我感觉除了电商业务,还有很多需要用到状态机的业务,但是熟悉业务的人不一定懂开发,如果有配置文件的话,是不是就可以直接让熟悉业务的人去写配置文件安排状态流转?当然,这可能只是一个不太成熟的想法。以上都是我自己一点小小的看法,这也是我的第一篇文章,欢迎资深的开发者们狠狠的指出我的不足。

相关推荐
青云计划10 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor35610 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor35610 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye11111 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai12 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟12 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事13 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊13 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
想用offer打牌14 小时前
MCP (Model Context Protocol) 技术理解 - 第五篇
人工智能·后端·mcp