當開發者意識到代碼庫開始變得般混亂不堪時,就會在現有項目中引入狀態機。狀態機的引入有助於將複雜多變的應用程式狀態轉換過程組織得更為有序和清晰,從而避免代碼陷入難以維護的境地。 ...
狀態機之所以強大,是因為其行為在啟動時就以固定的方式定義了操作規則,從而確保了一貫的連貫性和相對較高的可調試性。關鍵在於,應用程式處於且僅可能處於有限數量的狀態中。然後,某些事件發生會使得應用從一個狀態過渡到另一個狀態。狀態機由觸發器驅動,這些觸發器基於事件或計時器。
設計高層次邏輯並將其置於應用程式外部,然後通過多種方式與狀態機交互,這種方式要簡單得多。可以通過發送事件、監聽狀態機的行為或請求當前狀態來與狀態機進行交互。
當開發者意識到代碼庫開始變得般混亂不堪時,就會在現有項目中引入狀態機。麵條代碼表現為無盡的、層級化的IF、ELSE和BREAK子句結構,當事情變得過於複雜時,編譯器或許應該建議開發者暫停一下,先休息一下。狀態機的引入有助於將複雜多變的應用程式狀態轉換過程組織得更為有序和清晰,從而避免代碼陷入難以維護的境地。
什麼是狀態
狀態是狀態機可能處於的一種模型。相比於在通用文檔中使用抽象概念,通過現實生活中的例子來描述狀態通常更為直觀易懂。以一個簡單的鍵盤為例——我們大多數人每天都使用它。如果你有一個標準鍵盤,左側有普通鍵,右側有數字小鍵盤,你可能會註意到,根據Numlock(數字鎖定)是否激活,數字小鍵盤可以處於兩種不同的狀態。如果沒有激活,按下數字小鍵盤的按鍵會實現方嚮導航等功能;如果數字小鍵盤被激活,則按下這些鍵將輸入數字。本質上,鍵盤的數字小鍵盤部分可以處於兩種不同的狀態。
將狀態的概念聯繫到編程上,這意味著我們可以不再依賴於標誌位、嵌套的if/else/break語句或其他不切實際(有時甚至是曲折複雜的)邏輯,而是可以通過狀態、狀態變數或與狀態機的交互來處理問題。換句話說,在編程中運用狀態這一概念,能夠幫助我們更清晰地組織和管理程式的不同狀態及其轉換過程。
什麼是狀態機
狀態機是一種理論模型,它描述了一個對象在其生命周期內可能經歷的有限數量的狀態及其之間的轉換規則。每個狀態都有觸髮狀態遷移的條件(通常是事件),並且可以關聯執行的動作。
狀態機的核心在於狀態變遷和事件驅動,適合處理非同步和併發的情況。狀態機強調的是系統當前所處的狀態,並且關註於系統如何根據接收到的外部事件或內部條件進行狀態轉變。
狀態機最常見於嵌入式系統、用戶界面交互設計、游戲開發、網路協議解析等領域。
以下以游戲馬利奧的狀態切換為例,來理解狀態機的使用場景:
graph LR A[小馬利奧] -->|吃蘑菇| B[超級馬利奧] B -->|吃花| C[火焰馬利奧] C -->|被敵人碰到| B B -->|被敵人碰到| A與狀態設計模式的區別
在面向對象編程中,狀態設計模式是一種行為型設計模式,允許對象在其內部狀態改變時改變其行為。該模式通過將每一個狀態封裝成一個類,使得當對象的狀態發生改變時,它的行為也隨之改變,同時能夠使代碼更加清晰和模塊化。
在狀態設計模式中,每個狀態是一個單獨的類實例,這些類通常會實現一個公共介面,以便上下文對象可以調用適當的方法,而無需知道具體當前處於哪種狀態。上下文對象(context)持有對當前狀態對象的引用,併在接收到特定事件時調用狀態對象的方法來處理事件並可能導致狀態切換。
聯繫:
- 狀態設計模式是對狀態機理論的一種實現,它把狀態機的概念應用於軟體設計中,利用面向對象技術實現了狀態的抽象、封裝和擴展性。
區別:
- 狀態機是一個抽象的概念,可以不依賴於任何特定的編程語言或設計模式獨立存在。
- 狀態設計模式則是具體的編程實踐,是針對解決狀態轉換問題的一種設計解決方案,特別適用於面向對象環境下的複雜狀態管理。
與流程引擎的區別
流程引擎(Business Process Management Engine, BPMN Engine)是實現業務流程管理(BPM)的軟體組件,主要用於執行和監控預定義的工作流程。這些工作流程通常包括一系列順序執行的任務或活動,具有明確的開始點、結束點和中間過程。
流程引擎支持更複雜的流程結構,如並行分支、同步合併、迴圈等,並提供了豐富的建模語言(如BPMN)來可視化表示流程邏輯。流程引擎不僅關註狀態轉移,還註重任務分配、資源調度、事務處理以及流程實例的整體生命周期管理。
流程引擎適用於企業級應用中需要自動化、規範化和優化的複雜業務流程,比如採購審批流程、貸款審批流程、訂單處理流程等。
區別與聯繫:
- 目的性不同: 狀態機主要解決狀態變化的問題,而流程引擎則更多地關註流程的整體組織和執行。
- 結構靈活性: 狀態機結構相對簡單,特別適合清晰、固定的流程;流程引擎支持多層次、多路徑的複雜流程,允許動態調整和擴展。
- 參與角色: 狀態機側重於機器層面的自動化處理,流程引擎則常涉及人的參與決策和協同工作。
- 集成度: 流程引擎通常包含更多的管理和監控功能,能夠與組織其他系統緊密集成,提供強大的審計跟蹤、異常處理和數據分析能力。
- 聯繫: 在實際項目中,狀態機的概念和機制可能會作為流程引擎的一部分被採用,尤其是在流程中有明顯的狀態變遷環節時。同時,兩者都可以用作業務規則和流程規範的有效工具,只不過各自聚焦的領域和複雜程度有所差異。
什麼是Spring狀態機
Spring Statemachine(SSM)是一個框架,允許應用程式開發者在Spring應用中使用傳統的狀態機概念。SSM提供了以下功能:
- 為簡單用例提供易於使用的單層(一級)狀態機。
- 採用分層狀態機結構,便於配置複雜狀態。
- 狀態機區域以支持更為複雜的狀態配置。
- 支持觸發器、轉換、守衛和動作的使用。
- 提供類型安全的配置適配器。
- 集成了狀態機事件監聽器。
- 與Spring IoC(控制反轉)集成,可將Bean關聯至狀態機。
SSM有哪些使用場景
項目適於使用狀態機的場景包括:
- 當你可以將應用程式或其部分結構表示為一系列狀態時,該項目是應用狀態機的良好候選者。
- 你希望將複雜的邏輯拆分為更小、更易於管理的任務。
- 應用程式已經存在併發問題,例如非同步操作導致的問題。
在以下情況下,實際上你已經在嘗試實現一個狀態機:
- 使用布爾標誌或枚舉來模擬各種情況。這意味著你的代碼可能在通過這些標誌和枚舉跟蹤不同狀態。
- 擁有僅在應用程式生命周期中的某些階段才有意義的變數。這暗示了狀態變化對程式流程的影響。
- 正在迴圈遍歷if-else結構(或者更糟糕的是,多個這樣的結構),檢查特定標誌或枚舉是否已設置,然後根據這些標誌和枚舉是否存在及其組合進一步判斷接下來的操作。這種編程方式本質上是在手動處理狀態轉移,而採用狀態機可以更清晰、規範地表述並簡化此類複雜的狀態轉換邏輯。
如何集成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。
// 定義對應狀態和事件的枚舉:
public enum States {
SI, S1, S2
}
public enum Events {
E1, E2
}
// 定義狀態機的配置
import java.util.EnumSet;
@Configuration // 標識為配置類
@EnableStateMachine // 啟用狀態機功能
public class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
/**
* 配置狀態機的全局屬性,如自動啟動和狀態監聽器。
*
* @param config 狀態機配置構建器
* @throws Exception 如果配置過程中發生錯誤
*/
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true) // 設置狀態機自動啟動
.listener(listener()); // 註冊狀態改變監聽器
}
/**
* 配置狀態機的狀態。
*
* @param states 狀態配置構建器
* @throws Exception 如果配置過程中發生錯誤
*/
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.SI) // 設置初始狀態為SI
.states(EnumSet.allOf(States.class)); // 將所有枚舉狀態添加到狀態機
}
/**
* 配置狀態機的轉換。
*
* @param transitions 轉換配置構建器
* @throws Exception 如果配置過程中發生錯誤
*/
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal() // 配置外部觸發的轉換
.source(States.SI).target(States.S1).event(Events.E1) // 定義從SI到S1的轉換,由事件E1觸發
.and() // 連接另一個轉換配置
.withExternal() // 另一個外部觸發的轉換
.source(States.S1).target(States.S2).event(Events.E2); // 定義從S1到S2的轉換,由事件E2觸發
}
/**
* 創建並返回一個狀態機監聽器,用於監聽狀態的改變。
*
* @return 狀態機監聽器實例
*/
@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 = "狀態機", description = "狀態機")
public class StateController{
@Autowired
private StateService stateService;
@GetMapping(value = "改變狀態")
@Operation(description = "改變狀態")
public void change() {
stateService.changeState();
}
}
/**
* 狀態機演示服務
*/
@Service
public class StateService {
@Autowired
private StateMachine<States, Events> stateMachine;
public void changeState() {
stateMachine.sendEvent(Events.E1);
stateMachine.sendEvent(Events.E2);
}
}
服務層的輸出的結果如下:
State change to SI
State change from SI
State change to S1
State change from S1
State change to S2
以上代碼只是簡單演示了SSM的集成和使用demo。實際業務場景可能更為複雜,需要根據實際需求進行擴展。
除了狀態,要更好的使用SSM還需要理解偽狀態等很多概念,比如Junction(允許多個傳入轉換)、 Fork(一個或多個區域的顯式入口)、Join (將源自不同區域的多個過渡合併在一起)。這部分內容將在後續文章中進行介紹。
參考
- A State Machine Crash Course - spring-statemachine https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#crashcourse
關於作者
來自一線全棧程式員nine的八年探索與實踐,持續迭代中。歡迎關註公眾號“雨林尋北”或添加個人衛星codetrend(備註技術)。