Solon Framework

核心概念

狀態機有四個組成要素:

概念 意義
狀態 系統所處的條件(例如:「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) 選擇性執行動作

whenthen 都是選填。如果你只需要簡單的狀態轉移而不需副作用:

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