還在用ifelse來寫業(yè)務(wù)?了解下Spring狀態(tài)機(jī)

狀態(tài)機(jī)之所以強(qiáng)大,是因為其行為在啟動時就以固定的方式定義了操作規(guī)則,從而確保了一貫的連貫性和相對較高的可調(diào)試性。關(guān)鍵在于,應(yīng)用程序處于且僅可能處于有限數(shù)量的狀態(tài)中。然后,某些事件發(fā)生會使得應(yīng)用從一個狀態(tài)過渡到另一個狀態(tài)。狀態(tài)機(jī)由觸發(fā)器驅(qū)動,這些觸發(fā)器基于事件或計時器。

設(shè)計高層次邏輯并將其置于應(yīng)用程序外部,然后通過多種方式與狀態(tài)機(jī)交互,這種方式要簡單得多??梢酝ㄟ^發(fā)送事件、監(jiān)聽狀態(tài)機(jī)的行為或請求當(dāng)前狀態(tài)來與狀態(tài)機(jī)進(jìn)行交互。

當(dāng)開發(fā)者意識到代碼庫開始變得般混亂不堪時,就會在現(xiàn)有項目中引入狀態(tài)機(jī)。面條代碼表現(xiàn)為無盡的、層級化的IF、ELSE和BREAK子句結(jié)構(gòu),當(dāng)事情變得過于復(fù)雜時,編譯器或許應(yīng)該建議開發(fā)者暫停一下,先休息一下。狀態(tài)機(jī)的引入有助于將復(fù)雜多變的應(yīng)用程序狀態(tài)轉(zhuǎn)換過程組織得更為有序和清晰,從而避免代碼陷入難以維護(hù)的境地。

什么是狀態(tài)

狀態(tài)是狀態(tài)機(jī)可能處于的一種模型。相比于在通用文檔中使用抽象概念,通過現(xiàn)實生活中的例子來描述狀態(tài)通常更為直觀易懂。以一個簡單的鍵盤為例——我們大多數(shù)人每天都使用它。如果你有一個標(biāo)準(zhǔn)鍵盤,左側(cè)有普通鍵,右側(cè)有數(shù)字小鍵盤,你可能會注意到,根據(jù)Numlock(數(shù)字鎖定)是否激活,數(shù)字小鍵盤可以處于兩種不同的狀態(tài)。如果沒有激活,按下數(shù)字小鍵盤的按鍵會實現(xiàn)方向?qū)Ш降裙δ埽蝗绻麛?shù)字小鍵盤被激活,則按下這些鍵將輸入數(shù)字。本質(zhì)上,鍵盤的數(shù)字小鍵盤部分可以處于兩種不同的狀態(tài)。

將狀態(tài)的概念聯(lián)系到編程上,這意味著我們可以不再依賴于標(biāo)志位、嵌套的if/else/break語句或其他不切實際(有時甚至是曲折復(fù)雜的)邏輯,而是可以通過狀態(tài)、狀態(tài)變量或與狀態(tài)機(jī)的交互來處理問題。換句話說,在編程中運用狀態(tài)這一概念,能夠幫助我們更清晰地組織和管理程序的不同狀態(tài)及其轉(zhuǎn)換過程。

什么是狀態(tài)機(jī)

狀態(tài)機(jī)是一種理論模型,它描述了一個對象在其生命周期內(nèi)可能經(jīng)歷的有限數(shù)量的狀態(tài)及其之間的轉(zhuǎn)換規(guī)則。每個狀態(tài)都有觸發(fā)狀態(tài)遷移的條件(通常是事件),并且可以關(guān)聯(lián)執(zhí)行的動作。

狀態(tài)機(jī)的核心在于狀態(tài)變遷和事件驅(qū)動,適合處理異步和并發(fā)的情況。狀態(tài)機(jī)強(qiáng)調(diào)的是系統(tǒng)當(dāng)前所處的狀態(tài),并且關(guān)注于系統(tǒng)如何根據(jù)接收到的外部事件或內(nèi)部條件進(jìn)行狀態(tài)轉(zhuǎn)變。

狀態(tài)機(jī)最常見于嵌入式系統(tǒng)、用戶界面交互設(shè)計、游戲開發(fā)、網(wǎng)絡(luò)協(xié)議解析等領(lǐng)域。

以下以游戲馬里奧的狀態(tài)切換為例,來理解狀態(tài)機(jī)的使用場景:

graph LR
    A[小馬里奧] -->|吃蘑菇| B[超級馬里奧]
    B -->|吃花| C[火焰馬里奧]
    C -->|被敵人碰到| B
    B -->|被敵人碰到| A

與狀態(tài)設(shè)計模式的區(qū)別

在面向?qū)ο缶幊讨?,狀態(tài)設(shè)計模式是一種行為型設(shè)計模式,允許對象在其內(nèi)部狀態(tài)改變時改變其行為。該模式通過將每一個狀態(tài)封裝成一個類,使得當(dāng)對象的狀態(tài)發(fā)生改變時,它的行為也隨之改變,同時能夠使代碼更加清晰和模塊化。

在狀態(tài)設(shè)計模式中,每個狀態(tài)是一個單獨的類實例,這些類通常會實現(xiàn)一個公共接口,以便上下文對象可以調(diào)用適當(dāng)?shù)姆椒?,而無需知道具體當(dāng)前處于哪種狀態(tài)。上下文對象(context)持有對當(dāng)前狀態(tài)對象的引用,并在接收到特定事件時調(diào)用狀態(tài)對象的方法來處理事件并可能導(dǎo)致狀態(tài)切換。

聯(lián)系

  • 狀態(tài)設(shè)計模式是對狀態(tài)機(jī)理論的一種實現(xiàn),它把狀態(tài)機(jī)的概念應(yīng)用于軟件設(shè)計中,利用面向?qū)ο蠹夹g(shù)實現(xiàn)了狀態(tài)的抽象、封裝和擴(kuò)展性。

區(qū)別

  • 狀態(tài)機(jī)是一個抽象的概念,可以不依賴于任何特定的編程語言或設(shè)計模式獨立存在。
  • 狀態(tài)設(shè)計模式則是具體的編程實踐,是針對解決狀態(tài)轉(zhuǎn)換問題的一種設(shè)計解決方案,特別適用于面向?qū)ο蟓h(huán)境下的復(fù)雜狀態(tài)管理。

與流程引擎的區(qū)別

流程引擎(Business Process Management Engine, BPMN Engine)是實現(xiàn)業(yè)務(wù)流程管理(BPM)的軟件組件,主要用于執(zhí)行和監(jiān)控預(yù)定義的工作流程。這些工作流程通常包括一系列順序執(zhí)行的任務(wù)或活動,具有明確的開始點、結(jié)束點和中間過程。

流程引擎支持更復(fù)雜的流程結(jié)構(gòu),如并行分支、同步合并、循環(huán)等,并提供了豐富的建模語言(如BPMN)來可視化表示流程邏輯。流程引擎不僅關(guān)注狀態(tài)轉(zhuǎn)移,還注重任務(wù)分配、資源調(diào)度、事務(wù)處理以及流程實例的整體生命周期管理。

流程引擎適用于企業(yè)級應(yīng)用中需要自動化、規(guī)范化和優(yōu)化的復(fù)雜業(yè)務(wù)流程,比如采購審批流程、貸款審批流程、訂單處理流程等。

區(qū)別與聯(lián)系:

  • 目的性不同: 狀態(tài)機(jī)主要解決狀態(tài)變化的問題,而流程引擎則更多地關(guān)注流程的整體組織和執(zhí)行。
  • 結(jié)構(gòu)靈活性: 狀態(tài)機(jī)結(jié)構(gòu)相對簡單,特別適合清晰、固定的流程;流程引擎支持多層次、多路徑的復(fù)雜流程,允許動態(tài)調(diào)整和擴(kuò)展。
  • 參與角色: 狀態(tài)機(jī)側(cè)重于機(jī)器層面的自動化處理,流程引擎則常涉及人的參與決策和協(xié)同工作。
  • 集成度: 流程引擎通常包含更多的管理和監(jiān)控功能,能夠與組織其他系統(tǒng)緊密集成,提供強(qiáng)大的審計跟蹤、異常處理和數(shù)據(jù)分析能力。
  • 聯(lián)系: 在實際項目中,狀態(tài)機(jī)的概念和機(jī)制可能會作為流程引擎的一部分被采用,尤其是在流程中有明顯的狀態(tài)變遷環(huán)節(jié)時。同時,兩者都可以用作業(yè)務(wù)規(guī)則和流程規(guī)范的有效工具,只不過各自聚焦的領(lǐng)域和復(fù)雜程度有所差異。

什么是Spring狀態(tài)機(jī)

Spring Statemachine(SSM)是一個框架,允許應(yīng)用程序開發(fā)者在Spring應(yīng)用中使用傳統(tǒng)的狀態(tài)機(jī)概念。SSM提供了以下功能:

  1. 為簡單用例提供易于使用的單層(一級)狀態(tài)機(jī)。
  2. 采用分層狀態(tài)機(jī)結(jié)構(gòu),便于配置復(fù)雜狀態(tài)。
  3. 狀態(tài)機(jī)區(qū)域以支持更為復(fù)雜的狀態(tài)配置。
  4. 支持觸發(fā)器、轉(zhuǎn)換、守衛(wèi)和動作的使用。
  5. 提供類型安全的配置適配器。
  6. 集成了狀態(tài)機(jī)事件監(jiān)聽器。
  7. 與Spring IoC(控制反轉(zhuǎn))集成,可將Bean關(guān)聯(lián)至狀態(tài)機(jī)。

SSM有哪些使用場景

項目適于使用狀態(tài)機(jī)的場景包括:

  1. 當(dāng)你可以將應(yīng)用程序或其部分結(jié)構(gòu)表示為一系列狀態(tài)時,該項目是應(yīng)用狀態(tài)機(jī)的良好候選者。
  2. 你希望將復(fù)雜的邏輯拆分為更小、更易于管理的任務(wù)。
  3. 應(yīng)用程序已經(jīng)存在并發(fā)問題,例如異步操作導(dǎo)致的問題。

在以下情況下,實際上你已經(jīng)在嘗試實現(xiàn)一個狀態(tài)機(jī):

  1. 使用布爾標(biāo)志或枚舉來模擬各種情況。這意味著你的代碼可能在通過這些標(biāo)志和枚舉跟蹤不同狀態(tài)。
  2. 擁有僅在應(yīng)用程序生命周期中的某些階段才有意義的變量。這暗示了狀態(tài)變化對程序流程的影響。
  3. 正在循環(huán)遍歷if-else結(jié)構(gòu)(或者更糟糕的是,多個這樣的結(jié)構(gòu)),檢查特定標(biāo)志或枚舉是否已設(shè)置,然后根據(jù)這些標(biāo)志和枚舉是否存在及其組合進(jìn)一步判斷接下來的操作。這種編程方式本質(zhì)上是在手動處理狀態(tài)轉(zhuǎn)移,而采用狀態(tài)機(jī)可以更清晰、規(guī)范地表述并簡化此類復(fù)雜的狀態(tài)轉(zhuǎn)換邏輯。

如何集成SSM

需要在maven或者gradle中ssm的依賴。

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>4.0.0</version>
</dependency>
implementation 'org.springframework.statemachine:spring-statemachine-starter:4.0.0'

如何使用SSM

下面以一個簡單的例子來說明如何使用SSM。

// 定義對應(yīng)狀態(tài)和事件的枚舉:
public enum States {
    SI, S1, S2
}

public enum Events {
    E1, E2
}

// 定義狀態(tài)機(jī)的配置
import java.util.EnumSet;

@Configuration // 標(biāo)識為配置類
@EnableStateMachine // 啟用狀態(tài)機(jī)功能
public class StateMachineConfig
        extends EnumStateMachineConfigurerAdapter<States, Events> {

    /**
     * 配置狀態(tài)機(jī)的全局屬性,如自動啟動和狀態(tài)監(jiān)聽器。
     *
     * @param config 狀態(tài)機(jī)配置構(gòu)建器
     * @throws Exception 如果配置過程中發(fā)生錯誤
     */
    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config)
            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true) // 設(shè)置狀態(tài)機(jī)自動啟動
                .listener(listener()); // 注冊狀態(tài)改變監(jiān)聽器
    }

    /**
     * 配置狀態(tài)機(jī)的狀態(tài)。
     *
     * @param states 狀態(tài)配置構(gòu)建器
     * @throws Exception 如果配置過程中發(fā)生錯誤
     */
    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.SI) // 設(shè)置初始狀態(tài)為SI
                .states(EnumSet.allOf(States.class)); // 將所有枚舉狀態(tài)添加到狀態(tài)機(jī)
    }

    /**
     * 配置狀態(tài)機(jī)的轉(zhuǎn)換。
     *
     * @param transitions 轉(zhuǎn)換配置構(gòu)建器
     * @throws Exception 如果配置過程中發(fā)生錯誤
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal() // 配置外部觸發(fā)的轉(zhuǎn)換
                .source(States.SI).target(States.S1).event(Events.E1) // 定義從SI到S1的轉(zhuǎn)換,由事件E1觸發(fā)
                .and() // 連接另一個轉(zhuǎn)換配置
                .withExternal() // 另一個外部觸發(fā)的轉(zhuǎn)換
                .source(States.S1).target(States.S2).event(Events.E2); // 定義從S1到S2的轉(zhuǎn)換,由事件E2觸發(fā)
    }

    /**
     * 創(chuàng)建并返回一個狀態(tài)機(jī)監(jiān)聽器,用于監(jiān)聽狀態(tài)的改變。
     *
     * @return 狀態(tài)機(jī)監(jiān)聽器實例
     */
    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {
            @Override
            public void stateChanged(State<States, Events> from, State<States, Events> to) {
                if(from != null){
                    System.out.println("State change from " + from.getId());
                }
                System.out.println("State change to " + to.getId());
            }
        };
    }
}

測試代碼如下:

@RestController
@Tag(name = "狀態(tài)機(jī)", description = "狀態(tài)機(jī)")
public class StateController{
    @Autowired
    private StateService stateService;

    @GetMapping(value = "改變狀態(tài)")
    @Operation(description = "改變狀態(tài)")
    public void change() {
        stateService.changeState();
    }
}
/**
 * 狀態(tài)機(jī)演示服務(wù)
 */
@Service
public class StateService {
    @Autowired
    private StateMachine<States, Events> stateMachine;

    public void changeState() {
        stateMachine.sendEvent(Events.E1);
        stateMachine.sendEvent(Events.E2);
    }
}

服務(wù)層的輸出的結(jié)果如下:

State change to SI
State change from SI
State change to S1
State change from S1
State change to S2

以上代碼只是簡單演示了SSM的集成和使用demo。實際業(yè)務(wù)場景可能更為復(fù)雜,需要根據(jù)實際需求進(jìn)行擴(kuò)展。

除了狀態(tài),要更好的使用SSM還需要理解偽狀態(tài)等很多概念,比如Junction(允許多個傳入轉(zhuǎn)換)、 Fork(一個或多個區(qū)域的顯式入口)、Join (將源自不同區(qū)域的多個過渡合并在一起)。這部分內(nèi)容將在后續(xù)文章中進(jìn)行介紹。

參考

關(guān)于作者

來自一線全棧程序員nine的八年探索與實踐,持續(xù)迭代中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容