最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)現(xiàn)有限狀態(tài)機(jī)的推薦方案分享

 更新時(shí)間:2021年11月17日 10:33:43   作者:明明如月學(xué)長(zhǎng)  
有限狀態(tài)機(jī)又稱有限狀態(tài)自動(dòng)機(jī),簡(jiǎn)稱狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型,這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)有限狀態(tài)機(jī)的推薦方案,需要的朋友可以參考下

一、背景

平時(shí)工作開發(fā)過程中,難免會(huì)用到狀態(tài)機(jī)(狀態(tài)的流轉(zhuǎn))。

如獎(jiǎng)學(xué)金審批流程、請(qǐng)假審批流程、競(jìng)標(biāo)流程等,都需要根據(jù)不同行為轉(zhuǎn)到不同的狀態(tài)。

下面是一個(gè)簡(jiǎn)單的模擬狀態(tài)機(jī):

有些同學(xué)會(huì)選擇將狀態(tài)定義為常量,使用 if else 來流轉(zhuǎn)狀態(tài),不太優(yōu)雅。

有些同學(xué)會(huì)考慮將狀態(tài)定義為枚舉。

但是定義為枚舉之后,大多數(shù)同學(xué)會(huì)選擇使用 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;
        }
    }
}

上面這種寫法有幾個(gè)弊端:

(1) getByValue 每次獲取枚舉值都要循環(huán)一次當(dāng)前枚舉的所有常量,時(shí)間復(fù)雜度是
O(N),雖然耗時(shí)非常小,但總有些別扭,作為有追求的程序員,應(yīng)該盡量想辦法優(yōu)化掉。

(2) 總感覺使用 switch-case 實(shí)現(xiàn)狀態(tài)流轉(zhuǎn),更多的是面向過程的產(chǎn)物。雖然可以實(shí)現(xiàn)功能,但沒那么“面向?qū)ο蟆保热?State 枚舉就是用來表示狀態(tài),如果同意和拒絕可以通過 State 對(duì)象的方法獲取就會(huì)更直觀一些。

二、推薦方式

2.1 自定義的枚舉

通常狀態(tài)流轉(zhuǎn)有兩種方向,一種是贊同,一種是拒絕,分別流向不同的狀態(tài)。

由于本文討論的是有限狀態(tài),我們可以將狀態(tài)定義為枚舉比較契合,除非初態(tài)和終態(tài),否則贊同和拒絕都會(huì)返回一個(gè)狀態(tài)。

下面只是一個(gè)DEMO, 實(shí)際編碼時(shí)可以自由發(fā)揮。

該 Demo 的好處是:

1 使用 CACHE緩存,避免每次通過 value 獲取 State都循環(huán) State 枚舉數(shù)組

2 定義【同意】和【拒絕】抽象方法,每個(gè) State 通過實(shí)現(xiàn)該方法來流轉(zhuǎn)狀態(tài)。

3 狀態(tài)的定義和轉(zhuǎn)換都收攏在一個(gè)枚舉中,更容易維護(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),并實(shí)現(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();
}

測(cè)試代碼

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);


        // 獲取某個(gè)狀態(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)用實(shí)現(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 的方式實(shí)現(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)換是一個(gè) State 到另外一個(gè) State 的映射,每個(gè)狀態(tài)都應(yīng)該維護(hù)贊同和拒絕后的下一個(gè)狀態(tài)。

因此,我們很容易會(huì)聯(lián)想到使用【鏈表】來存儲(chǔ)這種關(guān)系 。

由于這里是外部枚舉,無法將狀態(tài)流轉(zhuǎn)在枚舉內(nèi)部完成(定義),就意味著我們還需要自定義狀態(tài)節(jié)點(diǎn)來表示流轉(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àn)閷?duì)于外部來說,只應(yīng)該感知 State 類,不應(yīng)該再去理解 StateNode ) , 提供贊同和拒絕方法,內(nèi)部通過拿到贊同和拒絕對(duì)應(yīng)的 StateNode 之后拿到對(duì)應(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)鏈模式” 的解決方案。

贊同和拒絕底層分別使用兩個(gè) Map 存儲(chǔ)。

為了更好地表達(dá)每次狀態(tài)的方向(即 Map 中的 key 和 value),每一個(gè)映射定義為 from 和 to 。

為了避免只有 from 沒有 to ,定義一個(gè)中間類型 SemiData,只有調(diào)用 to 之后才可以繼續(xù)鏈?zhǔn)骄幊滔氯?,最終構(gòu)造出狀態(tài)鏈。

以下結(jié)合 Map 的數(shù)據(jù)結(jié)構(gòu),結(jié)合升級(jí)版的 Builder 設(shè)計(jì)模式,實(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;
    }
}

測(cè)試方法

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("-------");

        // 獲取某個(gè)狀態(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)鏈,實(shí)現(xiàn)每個(gè)鏈的狀態(tài)各自流轉(zhuǎn)。而且性能非常好。

巧妙地將狀態(tài)的轉(zhuǎn)換定義和 Map 的定義合二為一,既能夠表意(from,to 比較明確),又能獲得很好的性能(獲取贊同和拒絕后的狀態(tài)轉(zhuǎn)化為
通過 key 取 Map 中的 value ),還有不錯(cuò)的編程體驗(yàn)(鏈?zhǔn)骄幊蹋?/p>

以上只是 DEMO,實(shí)際編碼時(shí),可自行優(yōu)化。

可能還有一些開源的包提供狀態(tài)機(jī)的功能,但核心原理大同小異。

三、總結(jié)

本文結(jié)合自己的理解,給出一種推薦的有限狀態(tài)機(jī)的寫法。

給出了自有狀態(tài)枚舉和外部狀態(tài)枚舉的解決方案,希望對(duì)大家有幫助。

通過本文,大家也可以看出,簡(jiǎn)單的問題深入思考,也可以得到不同的解法。

希望大家不要滿足現(xiàn)有方案,可以靈活運(yùn)用所學(xué)來解決實(shí)踐問題。

到此這篇關(guān)于Java實(shí)現(xiàn)有限狀態(tài)機(jī)的推薦方案的文章就介紹到這了,更多相關(guān)Java實(shí)現(xiàn)有限狀態(tài)機(jī)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring線程池ThreadPoolTaskExecutor配置詳情

    Spring線程池ThreadPoolTaskExecutor配置詳情

    本篇文章主要介紹了Spring線程池ThreadPoolTaskExecutor配置詳情,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • SpringBoot使用MapStruct生成映射代碼的示例詳解

    SpringBoot使用MapStruct生成映射代碼的示例詳解

    MapStruct 是一個(gè)用于 Java 的代碼生成器,專門用于生成類型安全的 bean 映射代碼,它通過注解處理器在編譯時(shí)生成映射代碼,從而避免了運(yùn)行時(shí)的性能開銷和潛在的錯(cuò)誤,本文給大家介紹了SpringBoot使用MapStruct生成映射代碼的示例,需要的朋友可以參考下
    2024-11-11
  • 基于springboot redirect重定向路徑問題總結(jié)

    基于springboot redirect重定向路徑問題總結(jié)

    這篇文章主要介紹了springboot redirect重定向路徑問題總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring Boot統(tǒng)一異常處理最佳實(shí)踐(拓展篇)

    Spring Boot統(tǒng)一異常處理最佳實(shí)踐(拓展篇)

    這篇文章主要給大家介紹了關(guān)于Spring Boot統(tǒng)一異常處理最佳實(shí)踐(拓展篇)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Springboot詳解如何實(shí)現(xiàn)SQL注入過濾器過程

    Springboot詳解如何實(shí)現(xiàn)SQL注入過濾器過程

    這篇文章主要介紹了基于springboot實(shí)現(xiàn)SQL注入過濾器,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2022-06-06
  • Idea 同一窗口導(dǎo)入多個(gè)項(xiàng)目的實(shí)現(xiàn)步驟

    Idea 同一窗口導(dǎo)入多個(gè)項(xiàng)目的實(shí)現(xiàn)步驟

    本文主要介紹了Idea 同一窗口導(dǎo)入多個(gè)項(xiàng)目的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java中線程的等待與喚醒_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java中線程的等待與喚醒_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當(dāng)前線程進(jìn)入等待狀態(tài),同時(shí),wait()也會(huì)讓當(dāng)前線程釋放它所持有的鎖。下面通過本文給大家介紹Java中線程的等待與喚醒知識(shí),感興趣的朋友一起看看吧
    2017-05-05
  • Java中如何實(shí)現(xiàn)不可變Map詳解

    Java中如何實(shí)現(xiàn)不可變Map詳解

    這篇文章主要給大家介紹了關(guān)于Java中如何實(shí)現(xiàn)不可變Map的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作工具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • 關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用

    關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用

    這篇文章主要介紹了關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Spring中實(shí)例化bean的四種方式詳解

    Spring中實(shí)例化bean的四種方式詳解

    這篇文章主要給大家介紹了關(guān)于Spring中實(shí)例化bean的四種方式,分別是setter 方法、構(gòu)造函數(shù)、靜態(tài)工廠以及實(shí)例工廠等四種方法,分別給出了示例代碼供大家參考學(xué)習(xí),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12

最新評(píng)論

神木县| 夹江县| 来凤县| 汕尾市| 万荣县| 密山市| 民丰县| 星座| 弥渡县| 漳浦县| 嵊州市| 呼图壁县| 五指山市| 太原市| 察隅县| 长春市| 青冈县| 淄博市| 寿光市| 龙岩市| 手机| 曲周县| 陆良县| 绿春县| 新和县| 平南县| 蕉岭县| 盱眙县| 外汇| 勃利县| 旺苍县| 古交市| 瑞丽市| 拜泉县| 区。| 兴业县| 克拉玛依市| 田阳县| 巢湖市| 灵台县| 星子县|