Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【Java】关于状态机的技术选型,最后一个真心好!

2023-12-09 11068点热度 0人点赞 0条评论

前言

今天想跟大家分享一个关于“状态机”的话题。状态属性在我们的现实生活中无处不在。比如电商场景会有一系列的订单状态(待支付、待发货、已发货、超时、关闭);员工提交请假申请会有申请状态(已申请、审核中、审核成功、审核拒绝、结束);差旅报销单会有单据审核状态(已提交、审核中、审核成功、退回、打款中、打款成功、打款失败、结束)等等。
上述场景有一个共同问题:根据不同触发条件执行不同处理动作最后落地不同的状态。
示例代码如下:
Integer status=0;
    if(condition1){
        status=1;
    }else if(condition2){
        status=2;
    }else if(condition3){
        status=3;
    }else if(condition4){
        status=4;
    }

那我们最容易能想到的自然是if-else方案。那if-else方案会有什么问题呢?

主要有以下几点:

  • 复杂的业务流程,if.else代码几乎无法维护
  • 随着业务的发展,业务过程也需要变更及扩展,但if.else代码段已经无法支持
  • 没有可读性,变更风险特别大,可能会牵一发而动全身,线上事故层出不穷
  • 其他业务逻辑可能也会跟if-else代码块耦合在一起,带来更多的问题
关于状态机的技术选型,最后一个真心好!
状态机的出现就是用来解决上述问题的。在复杂多状态流转情况下,通过状态机的引入,我们希望相关代码可读性、扩展性能比if-else方案更好!

关于状态机

▲什么是状态机

状态机是有限状态自动机的简称。有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。

关于有限的解释:也就是被描述的事物的状态的数量是有限的,例如开关的状态只有“开”和“关”两个;灯的状态只有“亮”和“灭”等等。
▲特点
一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到另一个状态,不过也可能存在瞬时状态,而一旦任务完成,状态机就会立刻离开瞬时状态。每个状态根据不同的前置条件,会从当前状态流转至下一个状态。
▲作用
使用状态机来表达状态的流转,会使语义会更加清晰,会增强代码的可读性和可维护性。
▲适用场景

面对复杂的状态流转(一般是超过三个及以上的状态流转),那么还是比较建议用状态机来实现的。

关于状态机的技术选型,最后一个真心好!

多状态示意图

状态机实现方案

▲枚举状态机

Java中的枚举是一个定义了一系列常量的特殊类(隐式继承自class java.lang.Enum)。枚举类型因为自身的线程安全性保障和高可读性特性,是简单状态机的首选。
关于线程安全说明
 
我们随便自定义一个枚举:
public enum OpinionsEnum {
    PASS,NOT_PASS
}

试着反编译上述代码:

public final class OpinionsEnum extends java.lang.Enum<OpinionsEnum> {
  public static final OpinionsEnum PASS;
  public static final OpinionsEnum NOT_PASS;
  public static OpinionsEnum[] values();
  public static OpinionsEnum valueOf(java.lang.String);
  static {};
}

通过反编译后的代码我们看到:OpinionsEnum它继承了java.lang.Enum类;class前的final标识告诉我们此枚举类不能被继承。

 

我们接着看它的两个属性:PASS、NOT_PASS。它们无一例外都经过了staic 的修饰,而我们知道staic修饰的属性会在类被加载之后就完成初始化,而这个过程是线程安全的。

 
示例代码:
public enum State {
    SUBMIT_APPLY {
        @Override
        State transition(String checkcondition) {
            System.out.println("员工提交请假申请单,同步流转到部门经理审批 参数 = " + checkcondition);
            return Department_MANAGER_AUDIT;
        }
    },
    Department_MANAGER_AUDIT {
        @Override
        State transition(String checkcondition) {
            System.out.println("部门经理审批完成,同步跳转到HR进行审批 参数 = " + checkcondition);
            return HR;
        }
    },
    HR {
        @Override
        State transition(String checkcondition) {
            System.out.println("HR完成审批,流转到结束组件, 参数 = " + checkcondition);
            return FINAL;
        }
    },
    FINAL {
        @Override
        State transition(String checkcondition) {
            System.out.println("流程结束, 参数 = " + checkcondition);
            return this;
        }
    };

    abstract State transition(String checkcondition);
}
public class StatefulObjectDemo {
    private  State state;

    public StatefulObjectDemo() {
        state = State.SUBMIT_APPLY;
    }

    public void performRequest(String checkCondition) {
        state = state.transition(checkCondition);
    }

    public static void main(String[] args) {
      StatefulObjectDemo theObject = new StatefulObjectDemo();
        theObject.performRequest("arg1");
        theObject.performRequest("arg2");
        theObject.performRequest("arg3");
        theObject.performRequest("arg4");

    }
}

输出:

员工提交请假申请单,同步流转到部门经理审批 参数 = arg1
部门经理审批完成,同步跳转到HR进行审批 参数 = arg2
HR完成审批,流转到结束组件, 参数 = arg3
流程结束, 参数 = arg4

 

Java枚举有一个比较有趣的特性即它允许为实例编写方法,从而为每个实例赋予其行为。实现也很简单,定义一个抽象的方法即可,这样每个实例必须强制重写该方法。(见示例的transition方法)

▲状态模式实现的状态机

是什么

状态模式是编程领域特有的名词,是 23 种设计模式之一,属于行为模式的一种。

它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

作用
状态模式的设计意图主要是为了解决两个主要问题:
  1. 当一个对象的内部状态改变时,它应该改变它的行为。
  2. 应独立定义特定于状态的行为。也就是说,添加新状态不应影响现有状态的行为。
类图:
 

关于状态机的技术选型,最后一个真心好!

类图

定义一个State接口,它可以有N个实现类,每个实现类需重写接口State定义的handle方法。它还有一个Context上下文类,内部持有一个State对象引用,外部状态发生改变(构造器内传入不同实现类),最终实现类自身行为动作也接着改变(实现类调用其自身的handle方法)。

Context示意图参考关于状态机的技术选型,最后一个真心好!

关于状态机的技术选型,最后一个真心好!

用状态模式实现的代码示例: 
public interface SwitchState {

    void handle();
}

public class TurnOffAction implements SwitchState{
    @Override
    public void handle() {
        System.out.println("关灯");
    }
}

public class TurnOnAction implements SwitchState{

    @Override
    public void handle() {
        System.out.println("开灯");
    }
}

public class Context {

    private SwitchState state;

    public Context(SwitchState state){
        this.state=state;
    }

    public void doAction(){
        state.handle();
    }
}

输出

public class StatePatternDemo {

    @DisplayName("状态模式测试用例-开灯")
    @Test
    public void turnOn() {
        Context context = new Context(new TurnOnAction());
        context.doAction();
    }

输出:开灯

    @DisplayName("状态模式测试用例-关灯")
    @Test
    public void turnOff() {
        Context context = new Context(new TurnOffAction());
        context.doAction();
    }
}

输出:关灯
大家看下这段示例代码:Context类有一个有参构造方法,参数类型是State,所以实例化对象的时候你可以传入State的不同的实现类。最终context.doAction()调用的是不同实现类的doAction方法。

▲开源实现

目前开源的状态机实现方案有spring-statemachine、squirrel-foundation、sateless4j等。其中spring-statemachine、squirrel-foundation在github上star和fork数稳居前二。

不过这些状态机普遍存在如下两个问题:

问题一:太复杂

因为基本囊括了UML State Machine上列举的所有功能,功能是强大了,但也搞得体积过于庞大、臃肿、很重。很多功能实际生产场景中根本用不到。

支持的高阶功能有:状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。大家可以对照一下这些功能你是否用的到。

问题二:性能差

‍

这些状态机都是有状态的(Stateful)的,有状态意味着多线程并发情况下如果是单个实例就容易出现线程安全问题。
在如今的普遍分布式多线程环境中,你就不得不每次一个请求就创建一个状态机实例。但问题来了一旦碰到某些状态机它的构建过程很复杂,如果当下QPS又很高话,往往会造成系统的性能瓶颈。
 
在这里我给大家推荐一款阿里开源的状态机:cola-statemachine。
github地址:
https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine
 
作者(张建飞:阿里高级技术专家)讲到面对复杂的状态流转,当时他们团队也想搞个状态机来减负,经过深思熟虑、不断类比之后他们考虑自研。希望能设计出一款功能相对简单、性能良好的开源状态机;最后命名为cola-component-statemachine(实现了内部DSL语法;目前最新版本:4.3.1)
示例代码:
//构建一个状态机(生产场景下,生产场景可以直接初始化一个Bean)
StateMachineBuilder<StateMachineTest.ApplyStates, StateMachineTest.ApplyEvents, Context> builder = StateMachineBuilderFactory.create();
      //外部流转(两个不同状态的流转)
      builder.externalTransition()
        .from(StateMachineTest.ApplyStates.APPLY_SUB)//原来状态
        .to(StateMachineTest.ApplyStates.AUDIT_ING)//目标状态
        .on(StateMachineTest.ApplyEvents.SUBMITING)//基于此事件触发
        .when(checkCondition1())//前置过滤条件
        .perform(doAction());//满足条件,最终触发的动作

 

上述代码先构建了一个状态机实例:from和to分别定义了源状态和目标状态,on定义了一个事件(状态机基于事件触发)当状态机匹配到指定的事件后,会进行条件过滤,如果满足指定条件,就会执行perform定义的动作函数,最终状态会从from内的源状态变成to定义的目标状态。

我们一起来看看客户端是怎么触发自定义的状态机的:

StateMachine<StateMachineTest.ApplyStates, StateMachineTest.ApplyEvents, Context> stateMachine = builder.build("ChoiceConditionMachine");
//fireEvent发送一个事件;对应上面示例代码的ApplyEvents.SUBMITING.
StateMachineTest.ApplyStates target1 = stateMachine.fireEvent(StateMachineTest.ApplyStates.APPLY_SUB, StateMachineTest.ApplyEvents.SUBMITING, new Context("pass"));
输出:
from:APPLY_SUB to:AUDIT_ING on:SUBMITING condition:pass

 

我把上述三款状态机的示例代码都放在了github上,有兴趣的小伙伴可以自行查阅。

github地址:

https://github.com/TaoZhuGongBoy/enumstatemachine

总结

好了,文章即将进入尾声,让我们一起来做个总结。

 

为什么引入状态机
前言部分我也提到了在面对复杂的状态流转场景下if-else方案主要容易引起可读性变差、可扩展能力弱、易出错等问题,所以引入状态机主要为了降低这些风险。
状态机的实现方案对比:
状态机实现方案我举例了Java枚举、状态模式、开源状态机等几个实现方案。状态模式的问题是它需要定义接口、和实现类还附带一个Context上下文类,编码层面比较复杂。Java枚举版的状态机主要问题是扩展粒度不够基本都是线性扩展,封装在一个类中,太复杂的状态流转这个类也会变得臃肿不堪,维护性变低。
所以也推荐了一款比较理想的开源状态机实现--cola-component-statemachine。它使用相当简单,因为实现了内部DSL,所以可读性很强,当然扩展性也比较不错。

 

 

本文仅供学习!所有权归属原作者。侵删!文章来源: 陶朱公Boy -陶朱公Boy :http://mp.weixin.qq.com/s/Em-nVLIVrWRXL4Fri8Wnyw

更多文章:

  1. FSM-COLA无状态状态机
  2. 实现一个状态机引擎,教你看清DSL的本质
  3. 设计模式在外卖营销业务中的实践
  4. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
  5. 26 条有效的AI提示词技巧
  6. 殷浩详解DDD 第四讲:领域层设计规范
  7. 殷浩详解DDD 第三讲 - Repository模式
  8. 解构领域驱动设计(三):领域驱动设计
  9. Eureka源码剖析之一:初始化-启动
  10. 浅析设计模式1 —— 工厂模式
标签: Java 状态机 cola fsm dsl 技术选型
最后更新:2023-12-09

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 前言
  • 关于状态机
  • 状态机实现方案
  • 总结
最新 热点 推荐
最新 热点 推荐
干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink? 如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?视频笔记:什么是AI 智能体?
【视频】NettyInAction作者:统治一切的框架Netty- One Framework to rule them all 记一次升级MySQL驱动包引发的事故 系统设计 | 数据字典方案 【进阶玩法】策略+责任链+组合实现合同签章 Arthas实战-线上热更新代码只需3步 浅析设计模式4——模板方法模式 干货 | Elasticsearch基础但非常有用的功能之二:模板 ChatGPT的探索与实践

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (2) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (18) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x