Java實現(xiàn)有限狀態(tài)機(jī)的推薦方案分享
一、背景
平時工作開發(fā)過程中,難免會用到狀態(tài)機(jī)(狀態(tài)的流轉(zhuǎn))。
如獎學(xué)金審批流程、請假審批流程、競標(biāo)流程等,都需要根據(jù)不同行為轉(zhuǎn)到不同的狀態(tài)。
下面是一個簡單的模擬狀態(tài)機(jī):

有些同學(xué)會選擇將狀態(tài)定義為常量,使用 if else 來流轉(zhuǎn)狀態(tài),不太優(yōu)雅。
有些同學(xué)會考慮將狀態(tài)定義為枚舉。
但是定義為枚舉之后,大多數(shù)同學(xué)會選擇使用 switch 來流轉(zhuǎn)狀態(tài):
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;
}
/**
* 批準(zhǔn)后的狀態(tài)
*/
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("當(dāng)前已終態(tài)");
}
}
/**
* 拒絕后的狀態(tài)
*/
public static State getRejectedState(State currentState) {
switch (currentState) {
case STATE_A:
throw new IllegalStateException("當(dāng)前狀態(tài)不支持拒絕");
case STATE_B:
case STATE_C:
case STATE_D:
default:
return STATE_A;
}
}
}
上面這種寫法有幾個弊端:
(1) getByValue 每次獲取枚舉值都要循環(huán)一次當(dāng)前枚舉的所有常量,時間復(fù)雜度是
O(N),雖然耗時非常小,但總有些別扭,作為有追求的程序員,應(yīng)該盡量想辦法優(yōu)化掉。
(2) 總感覺使用 switch-case 實現(xiàn)狀態(tài)流轉(zhuǎn),更多的是面向過程的產(chǎn)物。雖然可以實現(xiàn)功能,但沒那么“面向?qū)ο蟆?,既?State 枚舉就是用來表示狀態(tài),如果同意和拒絕可以通過 State 對象的方法獲取就會更直觀一些。
二、推薦方式
2.1 自定義的枚舉
通常狀態(tài)流轉(zhuǎn)有兩種方向,一種是贊同,一種是拒絕,分別流向不同的狀態(tài)。
由于本文討論的是有限狀態(tài),我們可以將狀態(tài)定義為枚舉比較契合,除非初態(tài)和終態(tài),否則贊同和拒絕都會返回一個狀態(tài)。
下面只是一個DEMO, 實際編碼時可以自由發(fā)揮。
該 Demo 的好處是:
1 使用 CACHE緩存,避免每次通過 value 獲取 State都循環(huán) State 枚舉數(shù)組
2 定義【同意】和【拒絕】抽象方法,每個 State 通過實現(xiàn)該方法來流轉(zhuǎn)狀態(tài)。
3 狀態(tài)的定義和轉(zhuǎn)換都收攏在一個枚舉中,更容易維護(hù)
雖然代碼看似更多一些,但是更“面向?qū)ο蟆币恍?/p>
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 {
/**
* 定義狀態(tài),并實現(xiàn)同意和拒絕的流轉(zhuǎn)
*/
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("當(dāng)前已終態(tài)");
}
@Override
State getRejectedState() {
return STATE_A;
}
};
@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);
}
/**
* 批準(zhǔn)后的狀態(tài)
*/
abstract State getApprovedState();
/**
* 拒絕后的狀態(tài)
*/
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);
// 獲取某個狀態(tài)的贊同和拒絕后的狀態(tài)
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());
}
}
輸出結(jié)果:
STATE_A-> approved:STATE_B
STATE_B-> approved:STATE_C
STATE_C-> approved:STATE_D
-----
STATE_B approved ->STATE_C
STATE_C reject ->STATE_A
STATE_D reject ->STATE_A
本質(zhì)上通過不同的方法調(diào)用實現(xiàn)自身的流轉(zhuǎn),而且贊同和拒絕定義為抽象類,可以“強(qiáng)迫”讓狀態(tài)的定義方明確自己的狀態(tài)流轉(zhuǎn)。
整體邏輯比較內(nèi)聚,狀態(tài)的定義和流轉(zhuǎn)都在 State 類中完成。
2.2 外部枚舉
假如該枚舉是外部提供,只提供枚舉常量的定義,不提供狀態(tài)流轉(zhuǎn),怎么辦?
我們依然可以采用 switch 的方式實現(xiàn)狀態(tài)流轉(zhuǎn):
import static basic.State.*;
public class StateUtils {
/**
* 批準(zhǔn)后的狀態(tài)
*/
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("當(dāng)前已經(jīng)是終態(tài)");
}
}
/**
* 拒絕后的狀態(tài)
*/
public static State getRejectedState(State currentState) {
switch (currentState) {
case STATE_A:
throw new IllegalStateException("當(dāng)前狀態(tài)不支持拒絕");
case STATE_B:
case STATE_C:
case STATE_D:
default:
return STATE_A;
}
}
}
還有更通用、更容易理解的編程方式呢(不用 switch)?
狀態(tài)機(jī)的每次轉(zhuǎn)換是一個 State 到另外一個 State 的映射,每個狀態(tài)都應(yīng)該維護(hù)贊同和拒絕后的下一個狀態(tài)。
因此,我們很容易會聯(lián)想到使用【鏈表】來存儲這種關(guān)系 。
由于這里是外部枚舉,無法將狀態(tài)流轉(zhuǎn)在枚舉內(nèi)部完成(定義),就意味著我們還需要自定義狀態(tài)節(jié)點來表示流轉(zhuǎn),如:
import lombok.Data;
@Data
public class StateNode<T> {
private T state;
private StateNode<T> approveNode;
private StateNode<T> rejectNode;
}
這樣構(gòu)造好鏈表以后,還需在工具類中要構(gòu)造 State 到 StateNode 的映射(因為對于外部來說,只應(yīng)該感知 State 類,不應(yīng)該再去理解 StateNode ) , 提供贊同和拒絕方法,內(nèi)部通過拿到贊同和拒絕對應(yīng)的 StateNode 之后拿到對應(yīng)的 State 返回即可。
偽代碼如下:
public class StateUtils{
// 構(gòu)造 StateNode 鏈表,和構(gòu)造 cache Map 略
private Map<State, StateNode<State>> cache ;
public State getApproveState(State current){
StateNode<State> node = cache.get(current);
return node == null? null: return node.getApproveNode().getState();
}
public State getRejectState(State current){
StateNode<State> node = cache.get(current);
return node == null? null: return node.getRejectNode().getState();
}
}
整體比較曲折,不如直接將贊同和拒絕定義在 State 枚舉內(nèi)更直觀。
下面給出一種 “狀態(tài)鏈模式” 的解決方案。
贊同和拒絕底層分別使用兩個 Map 存儲。
為了更好地表達(dá)每次狀態(tài)的方向(即 Map 中的 key 和 value),每一個映射定義為 from 和 to 。
為了避免只有 from 沒有 to ,定義一個中間類型 SemiData,只有調(diào)用 to 之后才可以繼續(xù)鏈?zhǔn)骄幊滔氯?,最終構(gòu)造出狀態(tài)鏈。
以下結(jié)合 Map 的數(shù)據(jù)結(jié)構(gòu),結(jié)合升級版的 Builder 設(shè)計模式,實現(xiàn)鏈?zhǔn)骄幊?/u>:
package basic;
import java.util.HashMap;
import java.util.Map;
public class StateChain<T> {
private final Map<T, T> chain;
private StateChain(Map<T, T> chain) {
this.chain = chain;
}
public T getNextState(T t) {
return chain.get(t);
}
public static <V> Builder<V> builder() {
return new Builder<V>();
}
static class Builder<T> {
private final Map<T, T> data = new HashMap<>();
public SemiData<T> from(T state) {
return new SemiData<>(this, state);
}
public StateChain<T> build() {
return new StateChain<T>(data);
}
public static class SemiData<T> {
private final T key;
private final Builder<T> parent;
private SemiData(Builder<T> builder, T key) {
this.parent = builder;
this.key = key;
}
public Builder<T> to(T value) {
parent.data.put(key, value);
return parent;
}
}
}
}
使用案例:
package basic;
import static basic.State.*;
public class StateUtils {
private static final StateChain<State> APPROVE;
private static final StateChain<State> REJECT;
static {
APPROVE = StateChain.<State>builder().from(STATE_A).to(STATE_B).from(STATE_B).to(STATE_C).from(STATE_C).to(STATE_D).build();
REJECT = StateChain.<State>builder().from(STATE_B).to(STATE_A).from(STATE_C).to(STATE_A).from(STATE_D).to(STATE_A).build();
}
/**
* 批準(zhǔn)后的狀態(tài)
*/
public static State getApprovedState(State currentState) {
State next = APPROVE.getNextState(currentState);
if(next == null){
throw new IllegalStateException("當(dāng)前已經(jīng)終態(tài)");
}
return next;
}
/**
* 拒絕后的狀態(tài)
*/
public static State getRejectedState(State currentState) {
State next = REJECT.getNextState(currentState);
if(next == null){
throw new IllegalStateException("當(dāng)前狀態(tài)不支持駁回");
}
return next;
}
}
測試方法
import static basic.State.STATE_B;
public class StateDemo {
public static void main(String[] args) {
State state = State.STATE_A;
// 一直贊同
State approvedState;
do {
approvedState = StateUtils.getApprovedState(state);
System.out.println(state + "-> approved:" + approvedState);
state = approvedState;
} while (state != State.STATE_D);
System.out.println("-------");
// 獲取某個狀態(tài)的贊同和拒絕后的狀態(tài)
System.out.println("STATE_B approved ->" + StateUtils.getApprovedState(STATE_B));
System.out.println("STATE_C reject ->" + StateUtils.getRejectedState(State.getByValue("C")));
System.out.println("STATE_D reject ->" + StateUtils.getRejectedState(State.getByValue("D")));
}
}
輸出結(jié)果
STATE_A-> approved:STATE_B
STATE_B-> approved:STATE_C
STATE_C-> approved:STATE_D
----
STATE_B approved ->STATE_C
STATE_C reject ->STATE_A
STATE_D reject ->STATE_A
這種方式更加靈活,可定義多條狀態(tài)鏈,實現(xiàn)每個鏈的狀態(tài)各自流轉(zhuǎn)。而且性能非常好。
巧妙地將狀態(tài)的轉(zhuǎn)換定義和 Map 的定義合二為一,既能夠表意(from,to 比較明確),又能獲得很好的性能(獲取贊同和拒絕后的狀態(tài)轉(zhuǎn)化為
通過 key 取 Map 中的 value ),還有不錯的編程體驗(鏈?zhǔn)骄幊蹋?/p>
以上只是 DEMO,實際編碼時,可自行優(yōu)化。
可能還有一些開源的包提供狀態(tài)機(jī)的功能,但核心原理大同小異。
三、總結(jié)
本文結(jié)合自己的理解,給出一種推薦的有限狀態(tài)機(jī)的寫法。
給出了自有狀態(tài)枚舉和外部狀態(tài)枚舉的解決方案,希望對大家有幫助。
通過本文,大家也可以看出,簡單的問題深入思考,也可以得到不同的解法。
希望大家不要滿足現(xiàn)有方案,可以靈活運用所學(xué)來解決實踐問題。
到此這篇關(guān)于Java實現(xiàn)有限狀態(tài)機(jī)的推薦方案的文章就介紹到這了,更多相關(guān)Java實現(xiàn)有限狀態(tài)機(jī)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用Java+Selenium+OpenCV模擬實現(xiàn)網(wǎng)頁滑動驗證
目前很多網(wǎng)頁都有滑動驗證,目的就是防止不良爬蟲扒他們網(wǎng)站的數(shù)據(jù)。本文將介紹通過Java Selenium OpenCV解決網(wǎng)頁滑塊驗證,需要的可以參考一下2022-01-01
Java實現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組
這篇文章主要介紹了Java實現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
細(xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)
下面小編就為大家?guī)硪黄?xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
Netty組件NioEventLoopGroup創(chuàng)建線程執(zhí)行器源碼解析
這篇文章主要介紹了Netty組件NioEventLoopGroup創(chuàng)建線程執(zhí)行器源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題
這篇文章主要介紹了mybatis foreach遍歷LIST讀到數(shù)據(jù)為null的問題,具有很好的參考價值,希望對大家有所幫助。2022-02-02
日常開發(fā)中,我們很多時候需要用到Java?8的Lambda表達(dá)式,它允許把函數(shù)作為一個方法的參數(shù),讓我們的代碼更優(yōu)雅、更簡潔。所以整理了一波工作中常用的Lambda表達(dá)式??赐暌欢〞袔椭?/div> 2022-11-11
Java 生成隨機(jī)字符串?dāng)?shù)組的實例詳解
這篇文章主要介紹了Java 生成隨機(jī)字符串?dāng)?shù)組的實例詳解的相關(guān)資料,主要是利用Collections.sort()方法對泛型為String的List 進(jìn)行排序,需要的朋友可以參考下2017-08-08最新評論

