前言
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代码块耦合在一起,带来更多的问题
关于状态机
状态机是有限状态自动机的简称。有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
关于有限的解释:也就是被描述的事物的状态的数量是有限的,例如开关的状态只有“开”和“关”两个;灯的状态只有“亮”和“灭”等等。
面对复杂的状态流转(一般是超过三个及以上的状态流转),那么还是比较建议用状态机来实现的。
多状态示意图
状态机实现方案
▲枚举状态机
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 种设计模式之一,属于行为模式的一种。
它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 当一个对象的内部状态改变时,它应该改变它的行为。
- 应独立定义特定于状态的行为。也就是说,添加新状态不应影响现有状态的行为。
类图
定义一个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(); } } 输出:关灯
▲开源实现
目前开源的状态机实现方案有spring-statemachine、squirrel-foundation、sateless4j等。其中spring-statemachine、squirrel-foundation在github上star和fork数稳居前二。
不过这些状态机普遍存在如下两个问题:
问题一:太复杂
因为基本囊括了UML State Machine上列举的所有功能,功能是强大了,但也搞得体积过于庞大、臃肿、很重。很多功能实际生产场景中根本用不到。
支持的高阶功能有:状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。大家可以对照一下这些功能你是否用的到。
问题二:性能差
//构建一个状态机(生产场景下,生产场景可以直接初始化一个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
总结
好了,文章即将进入尾声,让我们一起来做个总结。
本文仅供学习!所有权归属原作者。侵删!文章来源: 陶朱公Boy -陶朱公Boy :http://mp.weixin.qq.com/s/Em-nVLIVrWRXL4Fri8Wnyw
文章评论