核心思想
状态机有四个构建块:
| 概念 | 含义 |
|---|---|
| 状态 | 系统所处的条件(例如,“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>
进入全屏模式 退出全屏模式
现在,一个在 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")));
// 测试它
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);
}
}
}
进入全屏模式 退出全屏模式
运行它,灯会来回切换。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));
进入全屏模式 退出全屏模式
真实世界示例:订单状态机
让我们用状态 NONE -> CREATED -> PAID -> SHIPPED -> DELIVERED 建模一个订单生命周期,带有可选的取消路径。
public enum OrderEvent { CREATE, PAY, SHIP, DELIVER, CANCEL }
public enum OrderState { NONE, CREATED, PAID, SHIPPED, DELIVERED, CANCELLED }
进入全屏模式 退出全屏模式
定义一个实现 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; }
}
进入全屏模式 退出全屏模式
现在通过继承 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");
});
}
}
进入全屏模式 退出全屏模式
测试它:
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));
}
}
进入全屏模式 退出全屏模式
两种使用风格
1. Lambda 风格(组合)
StateMachine<State, Event, Payload> sm = new StateMachine<>();
sm.addTransition(t -> t.from(X).on(E).to(Y).then(ctx -> { ... }));
进入全屏模式 退出全屏模式
2. 继承风格
public class MyMachine extends StateMachine<State, Event, Payload> {
public MyMachine() {
from(X).on(E).to(Y).then(ctx -> { ... });
}
}
进入全屏模式 退出全屏模式
两者产生相同的结果。当你需要多个独立的机器时使用组合;当机器是一个自包含的领域概念时使用继承。
何时使用(以及何时不使用)
Solon 状态机被设计为 Solon Flow 的补充,而不是替代品。
在以下情况使用状态机:
- 你有离散的状态和事件(订单生命周期、文档工作流、游戏状态)
- 你想要声明式的替代方案来替代嵌套的 if-else 或 switch 语句
- 你不需要持久化 —— 状态通过事件上下文在内存中维护
在以下情况使用 Solon Flow:
- 你需要持久的、可恢复的长时间运行的流程
- 你需要为业务用户提供可视化设计器
- 你需要复杂的分支、并行执行或子流程
两者可以在同一个项目中共存。
真实情况
无持久化。 状态机在设计上是无状态的 —— 你通过 EventContext 传入当前状态,机器返回新状态。你负责存储。
无可视化设计器。 与 Solon Flow 拥有基于 Web 的设计器不同,状态机仅通过代码实现。
无内置事件总线集成。 状态转换同步触发 .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.