面向状态机编程:复杂业务逻辑应对之道
字数 1615 2025-08-11 08:35:55
面向状态机编程:复杂业务逻辑应对之道
一、状态机编程概述
状态机(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 定义状态枚举
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;
}
五、状态机设计最佳实践
-
明确状态和事件:在实现前先绘制状态转换图,明确所有可能的状态和触发转换的事件
-
状态枚举设计:
- 使用枚举明确所有可能的状态
- 为每个状态提供清晰的描述
- 实现根据状态码获取枚举的方法
-
事件枚举设计:
- 事件命名应清晰表达触发条件
- 事件应足够原子化
-
状态机配置:
- 将状态转换规则集中管理
- 为每个转换添加日志记录
- 考虑添加全局监听器处理异常情况
-
持久化:
- 状态变更时及时持久化到数据库
- 状态机重启时能从持久化存储恢复状态
-
线程安全:
- 每个业务实体使用独立的状态机实例
- 对关键操作添加同步控制
六、总结
状态机模式通过将复杂的状态流转规则显式化、集中化管理,实现了:
- 业务逻辑与状态管理的解耦
- 状态流转规则的清晰表达
- 系统可维护性和可扩展性的提升
对于具有复杂状态流转的业务场景,采用状态机模式能够显著降低系统复杂度,提高代码的可读性和可维护性。在实际项目中,可以结合Spring Statemachine等成熟框架快速实现状态机模式。