Java状态机(Squirrel & cola) 的学习

Java状态机(Squirrel & cola)

主要关注如下3个状态机:

spring-statemachinesquirrel statemachinecola-component-statemachine 。前两者是现在github上开源的最火的,也是对状态机功能实现的非常完整的项目,COLA-statemachine则是相对而言轻量且简单很多的实现

参考博客:

Cola-State Machine

一些很好的参考博客:

聊聊Cola-StateMachine轻量级状态机的实现-腾讯云开发者社区-腾讯云 (tencent.com)

Cola-StateMachine状态机的实战使用-腾讯云开发者社区-腾讯云 (tencent.com)

保姆式教程!如何使用Cola-statemachine构建高可靠性的状态机 ? - 掘金 (juejin.cn)

有一步步的介绍,挺好的:COLA中的cola-statemachine状态机理解与使用例_cola statemachine-优快云博客

有比较好的代码:COLA下的cola-statemachine状态机 - 简书 (jianshu.com)

与前两者的区别

这个其实是最新的的一个轻量化的开源框架,与其他的状态机不同的是它的状态机内部并没有维护一个“current state”。所以这个框架中的状态机严格来说不是状态机,是一个状态转移指导器。

详细来说,Spring State Machine和Squirrel 中的状态机内部存储来当前的状态,在外界给一个 next state 的信号给状态机后自动的就会跳转到下一个状态。 但是Cola-State Machine的状态机在使用的时候需要给 next state 的信号和 current state是什么,然后Cola-State Machine会告诉你下一个状态是什么。 状态机本身不维护“当前状态”这一信息,因此对于高并发场景非常好。

也就是说Cola-State Machine仅仅起到一个指导类型转换的功能,对于一种状态机,只需要实例化一个状态机就可以解决所有的状态转移需求。(10000个用于进程,Spring State Machine和Squirrel 需要有10000个状态机,Cola-State Machine只需要实例化一个状态机,当前状态什么的全部由用户进程自己保存。

缺点

太新了,现在的指导和开源项目实在是太少了,我什么没有找到专门的文档来,虽然轻量但是感觉需要实际用上的话可能难度比Squirrel还要大。

不过大概的学完的Squirrel之后回头来看发现Cola-State Machine 和 Squirrel其实在模型的创建和使用上还是非常的相似的。都是先生成一个builder然后建立状态转移,在状态转移的时候也能设置条件和要调用的函数,这一部分应该是Cola-State Machine参考的Squirrel的设计,用户使用的接口很类似。

Squirrel

英文文档:squirrel-foundation | Squirrel State Machine (hekailiang.github.io)

中文文档:Squirrel使用 | 城的灯 (yangguo.info)

状态机介绍:UML State Machine Diagrams - Overview of Graphical Notation (uml-diagrams.org)

有一个大好处,那就是它是一个成熟的框架,写代码的时候遇到了什么问题百度 google不出来直接去找ChatGPT大概率能够有一个还行的结果。所以最后选用了Squirrl实现任务。 但我实现完了之后看了一些Cola-State Machine的接口设计,感觉好像与Squirrl没啥很大的区别,对于一般的项目来说Cola-State Machine应该是更加适合的。

状态机的使用分为了构建和使用两种,这里直接给出个例子出来,以医学流程图这一需求为例,给定一张流程图,需要将这张流程图转变为状态机,期间涉及到了输入变量和根据输入变量的值进行条件转移。完全可以满足常见状态机的需求(不涉及状态并行什么的)。

注意给出的代码是不能直接跑的,只是一个使用的示例,帮助了解使用Squirrel状态机的步骤。

step1:构建状态、事件、条件

状态的话,随便写一个类即可,也不需要继承什么东西,这里的状态定义为: public abstract class BaseBox

public abstract class BaseBox {
    private String ID;
    private String text;
	// .....
}

事件是用于控制转移的,两个状态之间的转移需要有对应的事件

public enum MedEvent {
    TICK,
    RESET,
}

上下文简单理解就是用于存储具体的变量的一个类,比如在TICK 这一事件发生时,变量a<10时转移到一个状态,当a>=10 时到另一个状态中。这个变量就应该存在Context(上下文)中。这个类不需要继承特殊的类,跟State 一样自己写一个就可以了。

public class MedContext {
    private HashMap<String, Double> name2double;

    public MedContext() {
        this.name2double = new HashMap<>();
    }

    public MedContext(HashMap<String, Double> name2double) {
        this.name2double = name2double;
    }

    public void addVar(String name, Double value) {
        this.name2double.put(name, value);
    }

    public void addVar(HashMap<String, Double> var2value) {
        for (String name : var2value.keySet()) {
            this.name2double.put(name, var2value.get(name));
        }
    }

    public String getVarValue(String name) {
        if (!this.name2double.containsKey(name)) {
            return null;
        }
        return this.name2double.get(name).toString();
    }
}

条件是用与进一步控制状态转移的,比如在TICK 这一事件发生时,当变量a<10时转移到一个状态,当a>=10 时到另一个状态中。需要继承Condition<Object>,实现public boolean isSatisfied()public boolean isSatisfied(Object context)方法。

public class MedCondition implements Condition<Object> {

    private String name;
    private String boolExpression; // 用于判断的表达式
    private static final String regex = "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b";
    private static final Pattern pattern = Pattern.compile(regex);

    public MedCondition(String name) {
        this.name = name;
    }

    public MedCondition(String name, String boolExpression) {
        this.name = name;
        this.boolExpression = boolExpression;
    }

    @Override
    public boolean isSatisfied(Object context) {
        // 注意此处的context类为Object,必须得要在底下进行强制类型转换,不能直接写自己定义的MedContext
        MedContext medContext = (MedContext) context;
        return evaluateExpression(this.boolExpression, medContext.getName2double());
    }

    @Override
    public String name() {
        return name;
    }
}

使用如上的内容定义状态机。

@StateMachineParameters(stateType = BaseBox.class, eventType = MedEvent.class, contextType = MedContext.class)
public class MedStatemachine extends AbstractUntypedStateMachine {
    private BaseBox initialState;
    private String chartID;

	// 可以在这里定义出需要的函数,此处是输出当前状态机的信息
    public void printCurrentState() {
        assert this.getCurrentState() instanceof BaseBox;
        BaseBox currentState = (BaseBox) this.getCurrentState();
        System.out.println(currentState.to_dict())
        return;
    }
}
step2:构建出状态机
// 自己写的一个用于构建状态机的类
public class MedStatemachineBuilder {
    public static MedStatemachine build(String jsonString, Boolean isNeedFinalState) {
        // step1:构造出一个状态机构建器
        UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(MedStatemachine.class);

        // step2:构造出所有的状态
		// ...
        for (int i = 0; i < chartObjectArray.size(); i++) {
            BaseBox state = jsonNodeToBox(objJson);	// BaseBox对应的是状态机的“状态”
            if (isNeedFinalState && state.getBoxKind().equals(ConstBoxString.END_BOX)) {
                // 定义final state,到了final state之后再调用状态机会报错,如果是一般状态的话则不会有任何变化
                builder.defineFinalState(state); 
            } else {
                builder.defineState(state);
            }
        }

        // step3:构造出所有的转移
        HashMap<String, ArrayList<String>> decisionBox2Condition = new HashMap<>();
        for (int i = 0; i < chartObjectArray.size(); i++) {
            BaseBox fromBox = newBaseBox;
            BaseBox toBox = newBaseBox;
            if (needCondition) {
                // 有条件的转移
                String condition = newCondition;
                builder.externalTransition()
                        .from(fromBox)
                        .to(toBox)
                        .on(MedEvent.TICK)
                        .when(new MedCondition(newid, condition));
            } else {
                // 无条件转移,具体表现为没有When
                builder.externalTransition()
                        .from(id2Box.get(edgeJson.getJSONObject("source").getString("cell")))
                        .to(id2Box.get(edgeJson.getJSONObject("target").getString("cell")))
                        .on(MedEvent.TICK);
            }
        }

        // step4:完成构建状态机
        MedStatemachine medStatemachine = (MedStatemachine) builder.newStateMachine(initialBox);
        medStatemachine.setInitialState(initialBox);
        return medStatemachine;
    }
}
step3:使用状态机

两种,带有context的和不带的,但是事件(Event是一定要传的),主要是知道涉及到了when(condition)的状态机必须要传入context,这是condition给出判断结果的数据源。

// 带有Context的
medStatemachine.fire(MedEvent.TICK, this.medContext);

// 不带Context的
this.medStatemachine.fire(MedEvent.RESET);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值