极简状态机-Statemachine

背景

之前18年曾经写过一篇关于状态机的博客,链接如下:
https://www.jianshu.com/p/8def04b34b3c
在楼主当初的设计过程中,已经认为是非常完美的一篇关于状态机的文章了,后续,做相关交易系统的时候,觉得该状态机还是太重,过于复杂,于是,便重新设计了一套极简状态机,为什么说是极简状态机,因为这套状态机太过于简单,只有仅仅几个类,而且,轻依赖,仅仅依赖于lombok.jar和slf4j,用于生成set,get方法和打印日志。

为什么需要状态机

还是和上文一样,举个请假的例子,当我们需要请假的时候,首先需要领导审批,领导审批完成ceo审批,当然,并不是所有假条都能够审批完成,也有可能CEO拒绝这个假单,也可能直线领导拒绝这个假单,随着不同的审批意见,则会有不同的流程,去处理假条。
在这里,我们把假单看成业务单(可以是交易单,也可以是业务订单),状态机就是整个审批过程(审批过程的过程,审批链就是状态机),而审批意见就决定了状态机走哪条路。

举例

我们还是拿着请假来说事,先想想我们请假需要经过的几个步骤。

  • 提交假条
  • 领导审批
  • ceo审批

当然,也可能是到了领导,或者ceo审批终止,请假失败。
所以我们需要一个枚举类,来定义我们需要的步骤。

public enum LeavePermitState {
    SUBMIT_PERMIT,//提交假单
    LEADER_PERMIT,//领导审批
    CEO_PERMIT,//CEO审批
}

知道了该找谁批假条,也得看领导或者ceo批还是不批,所以还需要审批意见,在这里用一个枚举来定义,叫做event。这里为什么会有提交假单和领导审批,因为当你写完假单之后,你就自己会去找领导审批,而非有其他人督促你去提交假单,也就意味着有些步骤是你自己去驱动,自己决定要不要往下走。对于需要外部的意见,就只能等外部意见再接着走了。

public enum LeavePermitEvent {
    SUBMIT_PERMIT,//提交假单
    LEADER_PERMIT,//领导审批
    LEADER_PERMIT_AGREE,//领导审批通过
    LEADER_PERMIT_DISAGREE,//领导审批不通过
    CEO_PERMIT_AGREE,//ceo审批通过
    CEO_PERMIT_DISAGREE,//ceo审批不通过
}

审批动作和相关审批意见都已经有了,怎么把动作和审批意见关联起来,也就是状态机刻画出来,状态机怎么知道,这个意见是到了哪个动作去处理的?先画个图:


未命名文件 (15).png

解释一下:
当我们需要请假的时候,首先拿一个请假条(Event就是SUBMIT_COMMIT),然后填写相关请假信息(LeavePermitHandler),然后找领导审批(Event就是LEADER_PERMIT,意思是找领导审批),领导审批就会有两种不同意见,同意或者不同意,如果同意,则就要ceo审批,如果不同意,整个假条就失败了。ceo审批通过,则请假成功,假条才会到成功状态。怎么把这一系列动作刻画出来?在状态机里,可以在Spring上下文创建完成,加载状态机相关配置,也可以在静态方法中初始化。

public class StatemachineInit {

    //初始化状态机
    public static  void init(){
        //支持多状态机 这里以请假为例,可以支持多种
        StateMachineFactory.register("LEAVE_PERMIT",buildLeavePermitStateMachine());

    }

    private static StateMachine buildLeavePermitStateMachine() {
        StateMachineConfig<LeavePermitState,LeavePermitEvent,Handler> stateMachineConfig=new StateMachineConfig();

        stateMachineConfig.from(LeavePermitState.SUBMIT_PERMIT)//初始状态,提交假单
                .permit(LeavePermitEvent.SUBMIT_PERMIT) //拿假条
                .handle(new SubmitLeavePermitHandler())//填假条
                .to(LeavePermitState.LEADER_PERMIT) //填完之后,到领导审批
                 .build();

        stateMachineConfig.from(LeavePermitState.LEADER_PERMIT) //领导审批
                .permit(LeavePermitEvent.LEADER_PERMIT)  //待审批
                .handle(new LeaderPermitHandler())     //领导查阅
                .to(LeavePermitState.LEADER_PERMIT)    //查阅完,仍旧是领导审批状态
                .build();


        stateMachineConfig.from(LeavePermitState.LEADER_PERMIT) //领导审批
                .permit(LeavePermitEvent.LEADER_PERMIT_AGREE)   //领导同意
                .handle(new CeoPermitHandler())                 //领导同意之后CEO审批
                .to(LeavePermitState.CEO_PERMIT)                //ceo审批
                .build();

        stateMachineConfig.from(LeavePermitState.LEADER_PERMIT)  //领导审批
                .permit(LeavePermitEvent.LEADER_PERMIT_DISAGREE) //领导不同意
                .handle(new PermitFailHandler())                //假条失败
                .build();

        stateMachineConfig.from(LeavePermitState.CEO_PERMIT)   //CEO审批
                .permit(LeavePermitEvent.CEO_PERMIT_AGREE)      //ceo审批同意
                .handle(new PermitSuccessHandler())             //假条成功
                .build();


        stateMachineConfig.from(LeavePermitState.CEO_PERMIT)       //ceo审批
                .handle(new PermitFailHandler())                //ceo审批不通过
                .permit(LeavePermitEvent.CEO_PERMIT_DISAGREE)      //假条失败
                .build();

        return new StateMachine(stateMachineConfig);
    }
}

程序到底如何运行,才能跑起来?

 public static void main(String[] args){
        //初始化状态机
        StatemachineInit.init();
        log.info("创建假单");
        //创建上下文
        TransactionContext transactionContext=new TransactionContext();
        transactionContext.setData(LeavePermitContextConstants.CURRENT_STATE, LeavePermitState.SUBMIT_PERMIT);
        //开始请假,从拿请假条开始
        StateMachineFactory.getStateMachine("LEAVE_PERMIT").fire(LeavePermitEvent.SUBMIT_PERMIT, transactionContext);

        //拿假条,填写假条之后,需要领导审批
        log.info("领导审批");
        LeavePermit leavePermit=(LeavePermit)transactionContext.getData(LeavePermitContextConstants.LEAVE_PERMIT);
        TransactionContext transactionContext2=new TransactionContext();
        transactionContext2.setData(LeavePermitContextConstants.LEAVE_PERMIT,leavePermit);
        transactionContext2.setData(LeavePermitContextConstants.CURRENT_STATE, LeavePermitState.LEADER_PERMIT);
        //领导审批通过,同意该假单
        StateMachineFactory.getStateMachine("LEAVE_PERMIT").fire(LeavePermitEvent.LEADER_PERMIT_AGREE, transactionContext2);

        log.info("ceo审批");
        LeavePermit leavePermit2=(LeavePermit)transactionContext.getData(LeavePermitContextConstants.LEAVE_PERMIT);
        TransactionContext transactionContext3=new TransactionContext();
        transactionContext3.setData(LeavePermitContextConstants.LEAVE_PERMIT,leavePermit2);
        transactionContext3.setData(LeavePermitContextConstants.CURRENT_STATE, LeavePermitState.CEO_PERMIT);
        
          //ceo审批通过
        StateMachineFactory.getStateMachine("LEAVE_PERMIT").fire(LeavePermitEvent.CEO_PERMIT_AGREE, transactionContext3);
    }

有种类似开车的场景,状态机就像地图,指示牌就是Event,然后汽车是假条,当汽车走到某一个路口,会按照指示牌选择不同的路走,假条也是按照不同的Event(审批意见)在走,状态机和假条之前并没有必然关系,都是线程所有。不存在并发问题,就像汽车走在路上,然而路并和汽车没有关系,只是因为汽车上走的是路,所以状态机与业务并没有关系,而是业务跑在状态机上。
运行结果:

4:27:20.933 [main] INFO com.github.shxz130.statemachine.demo.main.Main - 创建假单
14:27:20.940 [main] INFO com.github.shxz130.statemachine.core.fire.StateMachine - [StateMachine] runing currentState=[SUBMIT_PERMIT], event=[SUBMIT_PERMIT], handle=[SubmitLeavePermitHandler], nextState=[LEADER_PERMIT]
14:27:20.944 [main] INFO com.github.shxz130.statemachine.demo.config.handler.SubmitLeavePermitHandler - [SubmitLeavePermitHandler],permit=[LeavePermit(permitNo=PERMITN, status=INIT)]
14:27:20.944 [main] INFO com.github.shxz130.statemachine.core.fire.StateMachine - [StateMachine] runing currentState=[LEADER_PERMIT], event=[LEADER_PERMIT], handle=[LeaderPermitHandler], nextState=[LEADER_PERMIT]
14:27:20.944 [main] INFO com.github.shxz130.statemachine.demo.config.handler.LeaderPermitHandler - [LeaderPermitHandler],permit=[LeavePermit(permitNo=PERMITN, status=LEADER_PERMIT)]
14:27:20.944 [main] INFO com.github.shxz130.statemachine.demo.config.handler.LeaderPermitHandler - 等待领导审批
14:27:20.944 [main] INFO com.github.shxz130.statemachine.demo.main.Main - 领导审批
14:27:20.945 [main] INFO com.github.shxz130.statemachine.core.fire.StateMachine - [StateMachine] runing currentState=[LEADER_PERMIT], event=[LEADER_PERMIT_AGREE], handle=[CeoPermitHandler], nextState=[CEO_PERMIT]
14:27:20.945 [main] INFO com.github.shxz130.statemachine.demo.config.handler.CeoPermitHandler - [CeoPermitHandler],permit=[LeavePermit(permitNo=PERMITN, status=CEO_PERMIT)]
14:27:20.945 [main] INFO com.github.shxz130.statemachine.demo.config.handler.CeoPermitHandler - 等待ceo审批
14:27:20.945 [main] INFO com.github.shxz130.statemachine.demo.main.Main - 领导审批
14:27:20.945 [main] INFO com.github.shxz130.statemachine.core.fire.StateMachine - [StateMachine] runing currentState=[CEO_PERMIT], event=[CEO_PERMIT_AGREE], handle=[PermitSuccessHandler], nextState=[null]
14:27:20.945 [main] INFO com.github.shxz130.statemachine.demo.config.handler.PermitSuccessHandler - [PermitSuccessHandler],permit=[LeavePermit(permitNo=PERMITN, status=SUCCESS)],审批意见:[{}]
Disconnected from the target VM, address: '127.0.0.1:61095', transport: 'socket'

这个例子写的比较粗,懂其意思即可。
感兴趣的可以看源码,我把状态机核心和demo做了分离,了解使用,仅关心demo即可,想了解原理,查看core即可。
相关源码见github:https://github.com/shxz130/statemachine

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 首先,了解状态机是什么,我们为什么需要状态机!举个最简单例子,请假,作为一个最底层程序员,每次请假都要领导层层审批...
    一滴水的坚持阅读 35,940评论 22 53
  • 那时候天总是很蓝,日子总过得太慢,你总说毕业遥遥无期,转眼就各奔东西…… 开心的是毕业考试很简单,遗憾的是...
    子孟非梦阅读 149评论 0 1
  • 有时候我看这些事件背后,单个人被挑出来的故事,我想过一个问题,关于一个个体的意义,一个个体情感是否值得被关注。这个...
    乐感日记阅读 204评论 0 0
  • --无戒365极限挑战日更营 第1天 很喜欢看周星驰的电影,尤其喜欢他说的一句话:“人要是没有梦想,和一条咸鱼有...
    落雪寻梅阅读 232评论 8 13