Java 实现有限状态机的推荐方案
文章来源:https://blog.csdn.net/w605283073/article/details/121345982
一、背景
平时工作开发过程中,难免会用到状态机(状态的流转)。
如奖学金审批流程、请假审批流程、竞标流程等,都需要根据不同行为转到不同的状态。
下面是一个简单的模拟状态机:
有些同学会选择将状态定义为常量,使用 if else 来流转状态,不太优雅。
有些同学会考虑将状态定义为枚举。
但是定义为枚举之后,大多数同学会选择使用来流转状态:
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/
import lombok.Getter; public enum State { STATE_A("A"), STATE_B("B"), STATE_C("C"), STATE_D("D"); @Getter private final String value; State(String value) { this.value = value; } public static State getByValue(String value) { for (State state : State.values()) { if (state.getValue().equals(value)) { return state; } } return null; } /** * 批准后的状态 */ public static State getApprovedState(State currentState) { switch (currentState) { case STATE_A: return STATE_B; case STATE_B: return STATE_C; case STATE_C: return STATE_D; case STATE_D: default: throw new IllegalStateException("当前已终态"); } } /** * 拒绝后的状态 */ public static State getRejectedState(State currentState) { switch (currentState) { case STATE_A: throw new IllegalStateException("当前状态不支持拒绝"); case STATE_B: case STATE_C: case STATE_D: default: return STATE_A; } } }
上面这种写法有几个弊端:
(1)每次获取枚举值都要循环一次当前枚举的所有常量,时间复杂度是O(N),虽然耗时非常小,但总有些别扭,作为有追求的程序员,应该尽量想办法优化掉。 (2) 总感觉使用 switch-case 实现状态流转,更多的是面向过程的产物。虽然可以实现功能,但没那么“面向对象”,既然 State 枚举就是用来表示状态,如果同意和拒绝可以通过 State 对象的方法获取就会更直观一些。
二、推荐方式
2.1 自定义的枚举
通常状态流转有两种方向,一种是赞同,一种是拒绝,分别流向不同的状态。
由于本文讨论的是有限状态,我们可以将状态定义为枚举比较契合,除非初态和终态,否则赞同和拒绝都会返回一个状态。
下面只是一个DEMO, 实际编码时可以自由发挥。
该 Demo 的好处是:
1 使用 CACHE缓存,避免每次通过 value 获取 State都循环 State 枚举数组
2 定义【同意】和【拒绝】抽象方法,每个 State 通过实现该方法来流转状态。
3 状态的定义和转换都收拢在一个枚举中,更容易维护
虽然代码看似更多一些,但是更“面向对象”一些。
package basic; import lombok.Getter; import java.util.Arrays; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public enum State { /** * 定义状态,并实现同意和拒绝的流转 */ STATE_A("A") { @Override State getApprovedState() { return STATE_B; } @Override State getRejectedState() { throw new IllegalStateException("STATE_A 不支持拒绝"); } }, STATE_B("B") { @Override State getApprovedState() { return STATE_C; } @Override State getRejectedState() { return STATE_A; } }, STATE_C("C") { @Override State getApprovedState() { return STATE_D; } @Override State getRejectedState() { return STATE_A; } }, STATE_D("D") { @Override State getApprovedState() { throw new IllegalStateException("当前已终态"); } @Override State getRejectedState() { return STATE_A; } }; // 更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/ @Getter private final String value; State(String value) { this.value = value; } private static final Map<String, State> CACHE; static { CACHE = Arrays.stream(State.values()).collect(Collectors.toMap(State::getValue, Function.identity())); } public static State getByValue(String value) { return CACHE.get(value); } /** * 批准后的状态 */ abstract State getApprovedState(); /** * 拒绝后的状态 */ abstract State getRejectedState(); }
测试代码
package basic; import static basic.State.STATE_B; public class StateDemo { public static void main(String[] args) { State state = State.STATE_A; // 一直赞同 State ApprovedState; do { ApprovedState = state.getApprovedState(); System.out.println(state + "-> Approved:" + ApprovedState); state = ApprovedState; } while (state != State.STATE_D); // 获取某个状态的赞同和拒绝后的状态 System.out.println("STATE_B Approved ->" + STATE_B.getApprovedState()); System.out.println("STATE_C reject ->" + State.getByValue("C").getRejectedState()); System.out.println("STATE_D reject ->" + State.getByValue("D").getRejectedState()); } }
package basic; import static basic.State.STATE_B; public class StateDemo { public static void main(String[] args) { State state = State.STATE_A; // 一直赞同 State approvedState; do { approvedState = state.getApprovedState(); System.out.println(state + "-> approved:" + approvedState); state = approvedState; } while (state != State.STATE_D); // 获取某个状态的赞同和拒绝后的状态 System.out.println("STATE_B approved ->" + STATE_B.getApprovedState()); System.out.println("STATE_C reject ->" + State.getByValue("C").getRejectedState()); System.out.println("STATE_D reject ->" + State.getByValue("D").getRejectedState()); } }
2.2 外部枚举
假如该枚举是外部提供,只提供枚举常量的定义,不提供状态流转,怎么办?
三、总结
本文结合自己的理解,给出一种推荐的有限状态机的写法。
给出了自有状态枚举和外部状态枚举的解决方案,希望对大家有帮助。
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/