面向状态机编程:复杂业务逻辑应对之道
字数 1615 2025-08-11 08:35:55

面向状态机编程:复杂业务逻辑应对之道

一、状态机编程概述

状态机(Finite-State Machine,FSM)是一种表示有限个状态以及在这些状态之间转移和行为的数学模型,特别适合处理具有明确状态流转的业务场景。

适用场景

  • 游戏编程中NPC的状态变化(跳跃、前进、转向等)
  • 电商领域订单状态流转
  • 操作系统进程调度
  • 任何具有明确状态转换规则的业务场景

二、状态机实现方式对比

2.1 if/else实现

优点

  • 实现简单直观
  • 适合状态数量少的场景

缺点

  • 状态增多时代码可读性差
  • 业务逻辑与状态判断深度耦合
  • 维护和扩展困难

2.2 状态模式

优点

  • 状态单独实现,可读性比if/else好
  • 符合开闭原则

缺点

  • 扩展状态需增加状态类
  • 状态多了会出现大量状态类
  • 未完全解耦状态与业务
  • 不利于了解系统状态全貌

2.3 有限状态机

优点

  • 基于严谨的数学模型
  • 状态转移和业务逻辑完全解耦
  • 可直观了解系统状态全貌
  • 便于维护和扩展

缺点

  • 需引入状态机实现框架
  • 有一定学习成本

三、有限状态机核心概念

3.1 关键要素

  1. 状态(State):系统可能处于的各种情况,在状态图中用圆圈表示
  2. 事件(Event):触发状态迁移的机制,对应状态图中的箭头
  3. 动作(Action):状态转换后执行的操作(可选)
  4. 转移(Transition):从原始状态到目标状态的过程
  5. 条件(Guard):状态转移需要满足的条件

3.2 Java状态机框架选型

框架 优点 缺点
Spring Statemachine 基于Spring生态,社区强大;功能完备;支持多种配置和持久化方式 较为重量级;单例模式状态机不保证线程安全;需通过工厂模式创建实例
Squirrel-foundation 轻量级实现;创建开销小;便于二次改造 社区不如Spring活跃;特殊约定较多

选型建议

  • Spring项目且不涉及高并发:选择Spring Statemachine
  • 需要轻量级实现或高度定制:选择Squirrel-foundation

四、Spring Statemachine实战

4.1 案例背景

零售采销维护SKU物流属性(长、宽、高、重量)时,需要经过审核流程,包含以下状态:

  1. 未操作(INIT)
  2. 任务下发中(TASK_DELIVERY)
  3. 下发失败(DELIVERY_FAIL)
  4. 复核中(RECHECKING)
  5. 已复核(RECHECKED)
  6. 自行联系供应商(CONCAT_SUPPLIER)
  7. 挂起(SUSPEND)

4.2 实现步骤

4.2.1 定义状态枚举

public enum SkuStateEnum {
    INIT(0, "未操作"),
    TASK_DELIVERY(1, "任务下发中"),
    DELIVERY_FAIL(2, "下发失败"),
    RECHECKING(3, "复核中"),
    RECHECKED(4, "已复核"),
    CONCAT_SUPPLIER(5, "自行联系供应商"),
    SUSPEND(6, "挂起");
    
    private Integer state;
    private String desc;
    
    // 构造方法、getter和根据state获取枚举的方法
}

4.2.2 定义事件枚举

public enum SkuAttrEventEnum {
    INVOKE_OMC_ATTR_COLLECT_API_SUCCESS,  // 调用OMC属性采集接口成功
    INVOKE_OMC_ATTR_COLLECT_API_FAIL,     // 调用OMC属性采集接口失败
    INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH, // 已生成采集单
    INVOKE_OMC_SKU_DELIVERY_API_FAIL,      // 下发查询接口失败
    MQ_OMC_SKU_ATTR_CHANGED,              // MQ返回SKU属性已变更
    INVOKE_SKU_ATTR_API_CHANGED,          // 调用接口返回SKU属性已变更
    HAS_JD_STOCK,                         // 京东有库存
    NO_JD_STOCK_HAS_VMI_STOCK,            // 京东无库存,VMI有库存
    NO_JD_STOCK_NO_VMI_STOCK,             // 京东和VMI均无库存
    UPLOAD_AND_RECHECK                    // 上传并复核
}

4.2.3 配置状态机

@Configuration
@EnableStateMachineFactory
public class SkuAttrStateMachineConfig extends StateMachineConfigurerAdapter<SkuStateEnum, SkuAttrEventEnum> {
    
    @Override
    public void configure(StateMachineStateConfigurer<SkuStateEnum, SkuAttrEventEnum> states) throws Exception {
        states.withStates()
            .initial(SkuStateEnum.INIT)
            .states(EnumSet.allOf(SkuStateEnum.class));
    }
    
    @Override
    public void configure(StateMachineTransitionConfigurer<SkuStateEnum, SkuAttrEventEnum> transitions) throws Exception {
        transitions
            // INIT -> TASK_DELIVERY (调用OMC属性采集接口成功)
            .withExternal()
                .source(SkuStateEnum.INIT)
                .target(SkuStateEnum.TASK_DELIVERY)
                .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
                .action(ctx -> log.info("状态变更: INIT -> TASK_DELIVERY"))
            .and()
            // INIT -> DELIVERY_FAIL (调用OMC属性采集接口失败)
            .withExternal()
                .source(SkuStateEnum.INIT)
                .target(SkuStateEnum.DELIVERY_FAIL)
                .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL)
                .action(ctx -> log.info("状态变更: INIT -> DELIVERY_FAIL"))
            // 其他状态转换配置...
            ;
    }
    
    // 配置监听器
    private StateMachineListener<SkuStateEnum, SkuAttrEventEnum> listener() {
        return new StateMachineListenerAdapter<SkuStateEnum, SkuAttrEventEnum>() {
            @Override
            public void transition(Transition<SkuStateEnum, SkuAttrEventEnum> transition) {
                log.info("状态变更: {} -> {}", 
                    transition.getSource().getId(), 
                    transition.getTarget().getId());
            }
            
            @Override
            public void eventNotAccepted(Message<SkuAttrEventEnum> event) {
                log.error("事件未收到: {}", event);
            }
        };
    }
}

4.2.4 构建状态机

public StateMachine<SkuStateEnum, SkuAttrEventEnum> buildStateMachine(String skuId) {
    // 从DB获取当前状态
    SkuStateEnum currentState = getStateFromDB(skuId);
    
    // 从工厂获取状态机实例
    StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = 
        stateMachineFactory.getStateMachine(skuId);
    
    // 配置状态机
    stateMachine.getStateMachineAccessor().doWithAllRegions(sma -> {
        sma.addStateMachineInterceptor(new StateMachineInterceptorAdapter<SkuStateEnum, SkuAttrEventEnum>() {
            @Override
            public void preStateChange(State<SkuStateEnum, SkuAttrEventEnum> state, 
                                      Message<SkuAttrEventEnum> message,
                                      Transition<SkuStateEnum, SkuAttrEventEnum> transition,
                                      StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine) {
                // 状态变更时更新数据库
                updateStateInDB(state.getId(), skuId);
            }
        });
        
        // 设置初始状态
        sma.resetStateMachine(new DefaultStateMachineContext<>(currentState, null, null, null));
    });
    
    stateMachine.start();
    return stateMachine;
}

4.2.5 发送事件

public Boolean sendEvent(StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
                        SkuAttrEventEnum event,
                        SkuAttrRecheckState stateInfo) {
    try {
        stateMachine.sendEvent(MessageBuilder.withPayload(event)
            .setHeader("SKU_ID", stateInfo.getSkuId())
            .setHeader("STATE", stateInfo.getState())
            .setHeader("JSON_STR", JSON.toJSONString(stateInfo))
            .build());
        return true;
    } catch (Exception e) {
        log.error("发送事件失败", e);
        return false;
    }
}

4.2.6 业务逻辑应用

public Boolean recheck(List<String> skuIds) {
    for (String skuId : skuIds) {
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = buildStateMachine(skuId);
        SkuAttrRecheckState stateInfo = getStateInfo(skuId);
        
        // 根据库存情况发送不同事件
        if (hasJdStock(skuId)) {
            invokeOmcApiAndSendEvent(stateMachine, stateInfo);
        } else if (hasVmiStock(skuId)) {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK, stateInfo);
        } else {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK, stateInfo);
        }
    }
    return true;
}

五、状态机设计最佳实践

  1. 明确状态和事件:在实现前先绘制状态转换图,明确所有可能的状态和触发转换的事件

  2. 状态枚举设计

    • 使用枚举明确所有可能的状态
    • 为每个状态提供清晰的描述
    • 实现根据状态码获取枚举的方法
  3. 事件枚举设计

    • 事件命名应清晰表达触发条件
    • 事件应足够原子化
  4. 状态机配置

    • 将状态转换规则集中管理
    • 为每个转换添加日志记录
    • 考虑添加全局监听器处理异常情况
  5. 持久化

    • 状态变更时及时持久化到数据库
    • 状态机重启时能从持久化存储恢复状态
  6. 线程安全

    • 每个业务实体使用独立的状态机实例
    • 对关键操作添加同步控制

六、总结

状态机模式通过将复杂的状态流转规则显式化、集中化管理,实现了:

  1. 业务逻辑与状态管理的解耦
  2. 状态流转规则的清晰表达
  3. 系统可维护性和可扩展性的提升

对于具有复杂状态流转的业务场景,采用状态机模式能够显著降低系统复杂度,提高代码的可读性和可维护性。在实际项目中,可以结合Spring Statemachine等成熟框架快速实现状态机模式。

面向状态机编程:复杂业务逻辑应对之道 一、状态机编程概述 状态机(Finite-State Machine,FSM)是一种表示有限个状态以及在这些状态之间转移和行为的数学模型,特别适合处理具有明确状态流转的业务场景。 适用场景 游戏编程中NPC的状态变化(跳跃、前进、转向等) 电商领域订单状态流转 操作系统进程调度 任何具有明确状态转换规则的业务场景 二、状态机实现方式对比 2.1 if/else实现 优点 : 实现简单直观 适合状态数量少的场景 缺点 : 状态增多时代码可读性差 业务逻辑与状态判断深度耦合 维护和扩展困难 2.2 状态模式 优点 : 状态单独实现,可读性比if/else好 符合开闭原则 缺点 : 扩展状态需增加状态类 状态多了会出现大量状态类 未完全解耦状态与业务 不利于了解系统状态全貌 2.3 有限状态机 优点 : 基于严谨的数学模型 状态转移和业务逻辑完全解耦 可直观了解系统状态全貌 便于维护和扩展 缺点 : 需引入状态机实现框架 有一定学习成本 三、有限状态机核心概念 3.1 关键要素 状态(State) :系统可能处于的各种情况,在状态图中用圆圈表示 事件(Event) :触发状态迁移的机制,对应状态图中的箭头 动作(Action) :状态转换后执行的操作(可选) 转移(Transition) :从原始状态到目标状态的过程 条件(Guard) :状态转移需要满足的条件 3.2 Java状态机框架选型 | 框架 | 优点 | 缺点 | |------|------|------| | Spring Statemachine | 基于Spring生态,社区强大;功能完备;支持多种配置和持久化方式 | 较为重量级;单例模式状态机不保证线程安全;需通过工厂模式创建实例 | | Squirrel-foundation | 轻量级实现;创建开销小;便于二次改造 | 社区不如Spring活跃;特殊约定较多 | 选型建议 : Spring项目且不涉及高并发:选择Spring Statemachine 需要轻量级实现或高度定制:选择Squirrel-foundation 四、Spring Statemachine实战 4.1 案例背景 零售采销维护SKU物流属性(长、宽、高、重量)时,需要经过审核流程,包含以下状态: 未操作(INIT) 任务下发中(TASK_ DELIVERY) 下发失败(DELIVERY_ FAIL) 复核中(RECHECKING) 已复核(RECHECKED) 自行联系供应商(CONCAT_ SUPPLIER) 挂起(SUSPEND) 4.2 实现步骤 4.2.1 定义状态枚举 4.2.2 定义事件枚举 4.2.3 配置状态机 4.2.4 构建状态机 4.2.5 发送事件 4.2.6 业务逻辑应用 五、状态机设计最佳实践 明确状态和事件 :在实现前先绘制状态转换图,明确所有可能的状态和触发转换的事件 状态枚举设计 : 使用枚举明确所有可能的状态 为每个状态提供清晰的描述 实现根据状态码获取枚举的方法 事件枚举设计 : 事件命名应清晰表达触发条件 事件应足够原子化 状态机配置 : 将状态转换规则集中管理 为每个转换添加日志记录 考虑添加全局监听器处理异常情况 持久化 : 状态变更时及时持久化到数据库 状态机重启时能从持久化存储恢复状态 线程安全 : 每个业务实体使用独立的状态机实例 对关键操作添加同步控制 六、总结 状态机模式通过将复杂的状态流转规则显式化、集中化管理,实现了: 业务逻辑与状态管理的解耦 状态流转规则的清晰表达 系统可维护性和可扩展性的提升 对于具有复杂状态流转的业务场景,采用状态机模式能够显著降低系统复杂度,提高代码的可读性和可维护性。在实际项目中,可以结合Spring Statemachine等成熟框架快速实现状态机模式。