写点什么

Squirrel 状态机 - 从原理探究到最佳实践

  • 2023-01-28
    北京
  • 本文字数:3064 字

    阅读完需:约 10 分钟

Squirrel状态机-从原理探究到最佳实践

作者:京东物流 郑朋辉

1 简介

Squirrel 状态机是一种用来进行对象行为建模的工具,主要描述对象在它的生命周期内所经历的状态,以及如何响应来自外界的各种事件。比如订单的创建、已支付、发货、收获、取消等等状态、状态之间的控制、触发事件的监听,可以用该框架进行清晰的管理实现。使用状态机来管理对象生命流的好处更多体现在代码的可维护性、可测试性上,明确的状态条件、原子的响应动作、事件驱动迁移目标状态,对于流程复杂易变的业务场景能大大减轻维护和测试的难度。

2 基本概念

2.1 Squirrel 状态机定义

Squirrel 状态机是一种有限状态机,有限状态机是指对象有一个明确并且复杂的生命流(一般而言三个以上状态),并且在状态变迁存在不同的触发条件以及处理行为。

2.2 Squirrel 状态机要素

Squirrel 状态机可归纳为 4 个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。


  • 现态:是指当前所处的状态。

  • 条件:又称为事件。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。

  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

  • 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

3 实现原理

3.1 店铺审核 CASE

举例,京东线上开店需要经过审核才能正式上线,店铺状态有待审核、已驳回、已审核,对应操作有提交审核,审核通过,审核驳回动作。现在需要实现一个店铺审核流程的需求。

3.2 方案对比

3.2.1 常用 if-else 或 switch-case 实现(分支模式)


图 1.if-else/switch-case 模式实现流程图

3.2.2 状态机实现


图 2.状态机模式实现流程图

3.2.3 对比

通过引入状态机,可以去除大量 if-else if-else 或者 switch-case 分支结构,直接通过当前状态和状态驱动表查询行为驱动表,找到具体行为执行操作,有利于代码的维护和扩展。

3.3 实现原理


图 3.状态机创建流程图


  • StateMachine: StateMachine 实例由 StateMachineBuilder 创建不被共享,对于使用 annotation 方式(或 fluent api)定义的 StateMachine,StateMachine 实例即根据此定义创建,相应的 action 也由本实例执行,与 spring 的集成最终要的就是讲 spring 的 bean 实例注入给由 builder 创建的状态机实例;

  • StateMachineBuilder: 本质上是由 StateMachineBuilderFactory 创建的动态代理。被代理的 StateMachineBuilder 默认实现为 StateMachineBuilderImpl,内部描述了状态机实例创建细节包括 State、Event、Context 类型信息、constructor 等,同时也包含了 StateMachine 的一些全局共享资源包括 StateConverter、EventConverter、MvelScriptManager 等。StateMachineBuilder 可被复用,使用中可被实现为 singleton;

  • StateMachineBuilderFactory: 为 StateMachineBuilder 创建的动态代理实例;

4 实践分享

4.1 环境依赖

<dependency><groupId>org.squirrelframework</groupId><artifactId>squirrel-foundation</artifactId><version>0.3.9</version></dependency>
复制代码

4.2 状态机元素定义:状态、事件

// 店铺审核状态public Enum ShopInfoAuditStatusEnum{audit(0,"待审核"),agree(1,"审核通过"),reject(2,"审核驳回");}// 店铺审核事件public Enum ShopInfoAuditEvent{SUBMIT, // 提交AGREE, // 同意REJECT; // 驳回}
复制代码

4.3 构建 StateMachineBuilder 实例

/*** StateMachineBuilder实例*/public class StateMachineEngine <T extends UntypedStateMachine, S, E, C> implements ApplicationContextAware{

private ApplicationContext applicationContext;

private static Map<String,UntypedStateMachineBuilder> builderMap = new HashMap<String,UntypedStateMachineBuilder>();
@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}

@Transactionalpublic void fire(Class<T> machine, S state, E event, C context) {StateMachineBuilder stateMachineBuilder = this.getStateMachineBuilder(machine);StateMachine stateMachine = stateMachineBuilder.newStateMachine(state,applicationContext);stateMachine.fire(event, context);}

private StateMachineBuilder getStateMachineBuilder(Class<T> stateMachine){UntypedStateMachineBuilder stateMachineBuilder = builderMap.get(stateMachine.getName());if(stateMachineBuilder == null){stateMachineBuilder = StateMachineBuilderFactory.create(stateMachine,ApplicationContext.class);builderMap.put(stateMachine.getName(),stateMachineBuilder);}return stateMachineBuilder;
复制代码

4.4 创建具体店铺状态审核状态机

/*** 店铺审核状态机*/@States({@State(name = "audit"),@State(name = "agree"),@State(name = "reject")})@Transitions({@Transit(from = "audit", to = "agree", on = "AGREE", callMethod = "agree"),@Transit(from = "audit", to = "reject", on = "REJECT", callMethod = "reject"),@Transit(from = "reject", to = "audit", on = "SUBMIT", callMethod = "submit"),@Transit(from = "agree", to = "audit", on = "SUBMIT", callMethod = "submit"),@Transit(from = "audit", to = "audit", on = "SUBMIT", callMethod = "submit"),})@StateMachineParameters(stateType=ShopInfoAuditStatusEnum.class, eventType=ShopInfoAuditEvent.class, contextType=ShopInfoAuditStatusUpdateParam.class)public class ShopInfoAuditStateMachine extends AbstractUntypedStateMachine {

private ApplicationContext applicationContext;

public ShopInfoAuditStateMachine(){}

public ShopInfoAuditStateMachine(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}

// 审核通过业务逻辑public void agree(ShopInfoAuditStatusEnum fromState, ShopInfoAuditStatusEnum toState, ShopInfoAuditEvent event, ShopInfoAuditStatusUpdateParam param) {this.agree(fromState,toState,event,param);}
// 审核驳回业务逻辑public void reject(ShopInfoAuditStatusEnum fromState, ShopInfoAuditStatusEnum toState, ShopInfoAuditEvent event, ShopInfoAuditStatusUpdateParam param) {this.reject(fromState,toState,event,param);}
// 提交业务逻辑public void submit(ShopInfoAuditStatusEnum fromState, ShopInfoAuditStatusEnum toState, ShopInfoAuditEvent event, ShopInfoAuditStatusUpdateParam param) {this.submit(fromState,toState,event,param);}
复制代码

4.5 客户端调用

// 调用端main{StateMachineEngine stateMachineEngine = applicationContext.getBean(StateMachineEngine.class);// 审核通过调casestateMachineEngine.fire(ShopInfoAuditStateMachine.class,ShopInfoAuditStatusEnum.audit,ShopInfoAuditEvent.AGREE,param);// 审核驳回casestateMachineEngine.fire(ShopInfoAuditStateMachine.class,ShopInfoAuditStatusEnum.audit,ShopInfoAuditEvent.REJECT,param);}
复制代码

5 总结

状态机很好的帮我们处理了对象状态的流转、事件的监听以及外界的各种事件的响应。从代码设计角度减少了大量 if-else/switch-case 逻辑判断,提高了代码的可维护性、扩展性,方便管理和测试。

发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Squirrel状态机-从原理探究到最佳实践_生命周期_京东科技开发者_InfoQ写作社区