狀態(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提供了以下功能:
- 為簡單用例提供易于使用的單層(一級)狀態(tài)機(jī)。
- 采用分層狀態(tài)機(jī)結(jié)構(gòu),便于配置復(fù)雜狀態(tài)。
- 狀態(tài)機(jī)區(qū)域以支持更為復(fù)雜的狀態(tài)配置。
- 支持觸發(fā)器、轉(zhuǎn)換、守衛(wèi)和動作的使用。
- 提供類型安全的配置適配器。
- 集成了狀態(tài)機(jī)事件監(jiān)聽器。
- 與Spring IoC(控制反轉(zhuǎn))集成,可將Bean關(guān)聯(lián)至狀態(tài)機(jī)。
SSM有哪些使用場景
項目適于使用狀態(tài)機(jī)的場景包括:
- 當(dāng)你可以將應(yīng)用程序或其部分結(jié)構(gòu)表示為一系列狀態(tài)時,該項目是應(yīng)用狀態(tài)機(jī)的良好候選者。
- 你希望將復(fù)雜的邏輯拆分為更小、更易于管理的任務(wù)。
- 應(yīng)用程序已經(jīng)存在并發(fā)問題,例如異步操作導(dǎo)致的問題。
在以下情況下,實際上你已經(jīng)在嘗試實現(xiàn)一個狀態(tài)機(jī):
- 使用布爾標(biāo)志或枚舉來模擬各種情況。這意味著你的代碼可能在通過這些標(biāo)志和枚舉跟蹤不同狀態(tài)。
- 擁有僅在應(yīng)用程序生命周期中的某些階段才有意義的變量。這暗示了狀態(tài)變化對程序流程的影響。
- 正在循環(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)行介紹。
參考
- A State Machine Crash Course - spring-statemachine https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#crashcourse
關(guān)于作者
來自一線全棧程序員nine的八年探索與實踐,持續(xù)迭代中。