引言
做过订单系统的小伙伴们一定对订单状态不会陌生,一个通用的订单状态演变可以如以下状态图所示:
由于每一个行为的发生都需要依赖于它的前置状态(如支付成功状态只能发生在PAYING支付中状态的订单上,不能发生在其他状态上),通常我们在实现相关的业务代码时,首先需要对对象的进行校验,比如如下所示:
对于学过编译原理的小伙伴来说,一眼就能看出这实际上就是一个有限状态机(finite-state machine, 表示有限个状态以及在这些状态之间的转移和 动作等行为的数学模型)。
为什么使用状态机
有小伙伴可能会问: 我在代码里用if-else就能实现相关逻辑,为什么要用状态机这种看上去很复杂的东西呢?
确实,在比较简单的逻辑中,if-else基本都能满足需求,用起来也简单,比如标记对象是否已删除(deleted = 0 or 1),这种简单的逻辑使用if-else 就足够了。但是对于对象复杂状态的管理,例如前文所说的订单对象的状态管理,在进行中间状态变更的时候需要写的if-else就复杂多了:某一个状态可能可以由多个状态演变过来,一个状态也可以根据不同的行为变成多个其他状态,代码的复杂度会显著提高;同时因为这些状态判断的代码 散落在各个地方,当业务逻辑变更涉及这些内容时,修改起来变得非常困难,一不小心就会遗漏需要修改的地方,然后导致对象状态发生了预期之外的变更。
而如果使用状态机来管理你的对象状态的话,相关状态机的代码就可以根据状态机的定义,对于对象状态变更进行强约束,状态机会拒绝非法的状态跃迁,从而保证对象状态绝对不会出现预期外的情况。同时在代码的相关地方,代码均不再需要各个地方都充斥着if-else判断,对于非法状态转 移,状态机会抛出异常,拒绝相关操作。
使用SMC工具实现一个状态机
SMC全称为The State Machine Compiler,是一个根据状态机定义可以编译生成符合状态模式(面向对象的方式实现状态机)的类。使用SMC来处理状态机逻辑有以下几个明显的好处:
1. 可以使用简洁的语言来定义状态机。3. 不需要手动维护状态转移矩阵。
4. 不需要写一大堆的if-else或者switch语句。
5. 可以自动生成状态机示意图(如本文最上方订单类的状态图)
SMC工具本身是用JAVA写的,但是可以生成多种语言的代码(C/C++, JAVA, JavaScript,Python等),本文我们就以JAVA为例简单介绍下使用方法:
定义一个SMC .sm文件
.sm文件是用来定义状态机的文件,例如我们创建一个 OrderStateFSM.sm文件用来描述订单状态。 首先我们需要提供一些基本的信息:
首先我们需要提供一些基本的信息:
定义状态机的状态
然后我们在上面留空的地方开始定义订单基本的状态(待支付、支付超时、物流运输中、成功、退款中、退款成功六个状态):
定义状态机的状态转移
状态转移的定义包含四部分:
- 转移的命名
- 可选的转移保护
- 转移的结束状态
- 转移触发的行为
我们定一个支付成功(PaySuccess)的状态转移,订单状态由支付中(PAYING)变成发货中(SHIPPING),并触发开始送货的行为 (startShipping),如下:
编译.sm文件
定义完整个状态机之后,我们需要使用SMC提供的Smc.jar将.sm文件编译成对应语言的类文件,此处以编译成JAVA语言为例,执行:
执行完毕后会在目录下生成OrderStateFSM.java源文件。
添加进项目
- 编译后生成的源文件OrderStateFSM.java需要放到项目里和对应领域对象(此处是Order.java)处于同一个package下
- 添加statemap.jar到项目依赖(SMC并没有把这个jar发布到maven,因此只能手动加到项目里)
做完以上两步之后编译生成的代码应该可以在项目里正常编译通过了。接下来我们就需要在Order类里面使用生成的状态机: 先定义为Order类的成员变量,然后在构造方法里面初始化,以及实现转移触发的行为方法:
之后就可以在Order类的方法里面使用状态机来触发状态转移了,比如我们收到了支付成功的消息,需要更新Order类:
从中我们可以看到,状态机帮我们完成了状态转移合法性校验的步骤,我们只需要去关心startShipping的实现即可,省去了判断订单状态是否合法 的代码。
生成状态机图
SMC除了可以帮我们根据定义的sm文件生成状态机实现代码之外,还可以额外生成状态机图片(如本文开头的订单状态机图片)使用如下命令:
运行以上命令之后就会在当前目录生成相应的PNG文件。
总结
SMC是一款多语言支持的、可以生成符合状态模式的状态机代码的工具,大大简化了维护相关业务逻辑的复杂性,强烈建议大家可以在日常的项目 中应用!更多资料请参考官方网站。
SMC官网:smc.sourceforge.net