核心概念
狀態機有四個組成要素:
| 概念 | 意義 |
|---|---|
| 狀態 | 系統所處的條件(例如:「CREATED」、「PAID」) |
| 事件 | 所發生的事情(例如:「PAY」、「SHIP」) |
| 動作 | 轉移發生時執行的程式碼 |
| 轉移 | 規則:從狀態 X 收到事件 Y,轉到狀態 Z(可選擇性附帶條件 W 並執行動作 A) |
Solon 的 StateMachine<State, Event, Payload> 透過流暢的 DSL 將它們串聯在一起。
Hello World:燈光開關
加入依賴:
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-statemachine</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode
現在建立一個可在 OFF 與 ON 之間切換的燈光開關:
import org.noear.solon.statemachine.EventContext;
import org.noear.solon.statemachine.StateMachine;
public class LightSwitchApp {
enum Event { PRESS }
enum State { OFF, ON }
public static void main(String[] args) {
StateMachine<State, Event, String> stateMachine = new StateMachine<>();
// OFF + PRESS -> ON
stateMachine.addTransition(t -> t.from(State.OFF).on(Event.PRESS).to(State.ON)
.then(context -> System.out.println("Light is ON")));
// ON + PRESS -> OFF
stateMachine.addTransition(t -> t.from(State.ON).on(Event.PRESS).to(State.OFF)
.then(context -> System.out.println("Light is OFF")));
// Test it
State currentState = State.OFF;
for (int i = 0; i < 6; i++) {
currentState = stateMachine.sendEvent(
Event.PRESS,
EventContext.of(currentState, null)
);
System.out.println("New state: " + currentState);
}
}
}
Enter fullscreen mode Exit fullscreen mode
執行後,燈光就會在 ON/OFF 之間來回切換。 sendEvent() 會回傳新狀態——你不需要手動管理狀態變數,只需把目前狀態交給狀態機,它就會幫你計算下一個狀態。
DSL 拆解
StateTransitionDecl 提供五個方法:
| 方法 | 必要 | 說明 |
|---|---|---|
.from(state) |
是 | 來源狀態(可有多個) |
.to(state) |
是 | 目標狀態(只有一個) |
.on(event) |
是 | 觸發事件 |
.when(condition) |
否 | 選擇性守衛條件 |
.then(action) |
否 | 選擇性執行動作 |
when 與 then 都是選填。如果你只需要簡單的狀態轉移而不需副作用:
stateMachine.addTransition(t ->
t.from(State.DRAFT).to(State.PUBLISHED).on(Event.PUBLISH));
Enter fullscreen mode Exit fullscreen mode
真實案例:訂單狀態機
我們來建立一個訂單生命週期:NONE -> CREATED -> PAID -> SHIPPED -> DELIVERED,並提供取消路徑。
public enum OrderEvent { CREATE, PAY, SHIP, DELIVER, CANCEL }
public enum OrderState { NONE, CREATED, PAID, SHIPPED, DELIVERED, CANCELLED }
Enter fullscreen mode Exit fullscreen mode
定義實作 EventContext 的 Order 承載物件:
public class Order implements EventContext<OrderState, Order> {
private String id;
private String product;
private OrderState state;
private String status;
@Override
public OrderState getState() { return state; }
@Override
public Order getPayload() { return this; }
public void setState(OrderState state) { this.state = state; }
public void setStatus(String status) { this.status = status; }
}
Enter fullscreen mode Exit fullscreen mode
接著透過繼承 StateMachine 來建立狀態機:
public class OrderStateMachine extends StateMachine<OrderState, OrderEvent, Order> {
public OrderStateMachine() {
from(OrderState.NONE).on(OrderEvent.CREATE).to(OrderState.CREATED).then(ctx -> {
Order payload = ctx.getPayload();
payload.setState(OrderState.CREATED);
payload.setStatus("Created");
});
from(OrderState.CREATED).on(OrderEvent.PAY).to(OrderState.PAID).then(ctx -> {
Order payload = ctx.getPayload();
payload.setState(OrderState.PAID);
payload.setStatus("Paid");
});
from(OrderState.PAID).on(OrderEvent.SHIP).to(OrderState.SHIPPED).then(ctx -> {
Order payload = ctx.getPayload();
payload.setState(OrderState.SHIPPED);
payload.setStatus("Shipped");
});
from(OrderState.SHIPPED).on(OrderEvent.DELIVER).to(OrderState.DELIVERED).then(ctx -> {
Order payload = ctx.getPayload();
payload.setState(OrderState.DELIVERED);
payload.setStatus("Delivered");
});
}
}
Enter fullscreen mode Exit fullscreen mode
測試程式:
public class OrderTest {
public static void main(String[] args) {
OrderStateMachine stateMachine = new OrderStateMachine();
Order order = new Order("1", "iPhone 16 Pro Max", null);
stateMachine.sendEvent(OrderEvent.CREATE, order);
stateMachine.sendEvent(OrderEvent.PAY, order);
stateMachine.sendEvent(OrderEvent.SHIP, EventContext.of(order.getState(), order));
stateMachine.sendEvent(OrderEvent.DELIVER, EventContext.of(order.getState(), order));
}
}
Enter fullscreen mode Exit fullscreen mode
兩種使用方式
1. Lambda 風格(組合)
StateMachine<State, Event, Payload> sm = new StateMachine<>();
sm.addTransition(t -> t.from(X).on(E).to(Y).then(ctx -> { ... }));
Enter fullscreen mode Exit fullscreen mode
2. 繼承風格
public class MyMachine extends StateMachine<State, Event, Payload> {
public MyMachine() {
from(X).on(E).to(Y).then(ctx -> { ... });
}
}
Enter fullscreen mode Exit fullscreen mode
兩種方式結果相同。當你需要多個獨立的狀態機時,請使用組合方式;當狀態機本身是領域概念時,建議使用繼承方式。
何時使用(以及何時不使用)
Solon 狀態機設計為 Solon Flow 的補充,而非替代。
適合使用狀態機的情境:
- 你擁有離散的狀態與事件(訂單生命週期、文件流程、遊戲狀態)
- 你想要以宣告式方式取代巢狀 if-else 或 switch 陳述式
- 你不需要持久化——狀態由事件上下文在記憶體中維護
適合使用 Solon Flow 的情境:
- 你需要持久化、可恢復的長時間執行流程
- 你需要給業務人員使用的視覺化設計工具
- 你需要複雜分支、平行執行或子流程
兩者可以在同一個專案中並存。
真實面貌
無持久化。 狀態機設計上為無狀態——你透過 EventContext 傳入目前狀態,狀態機會回傳新狀態。儲存由你自己負責。
無視覺化設計工具。 不同於 Solon Flow 提供網頁設計工具,狀態機純以程式碼撰寫。
無內建事件匯流排整合。 狀態轉移會同步觸發 .then() 動作。
簡單但不適合複雜工作流。 如果你的狀態邏輯涉及平行狀態或巢狀狀態機,Solon Flow 是更好的選擇。
摘要
| 面向 | 說明 |
|---|---|
| 依賴 |
org.noear:solon-statemachine(自 v3.4.3 起) |
| 核心類別 | StateMachine<State, Event, Payload> |
| DSL | .from().on().to().when().then() |
| 上下文 |
EventContext.of(state, payload) 或自訂實作 |
| 持久化 | 無(由呼叫端擁有、透過上下文傳遞) |
| 最適合用於 | 訂單生命週期、文件流程、遊戲狀態、切換開關 |
本文所有範例皆來自官方文件 solon.noear.org。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.