关于状态机——用事件触发状态

一、为什么要学习状态机?🤔

​ 在最近的学习与实践过程中,笔者频繁地遇到需要设置并变更对象状态的情形,这一过程促使我去探索更为系统化的解决方案——状态机。为了更好地引入这个概念,让我们从日常生活中最常见的例子说起:红绿灯。就像红绿灯明确指示着车辆何时停止、何时行进一样,状态机也为我们提供了一套清晰且有序的框架,用以管理和转换软件系统中复杂的状态逻辑。通过学习和应用状态机,我们不仅能够有效避免代码中的混乱与冗余,还能大幅提升系统的可维护性和扩展性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TrafficLight {
private String state = "RED"; // 当前状态:RED, YELLOW, GREEN

public void change() {
if (state.equals("RED")) {
state = "GREEN";
System.out.println("红灯 → 绿灯");
} else if (state.equals("GREEN")) {
state = "YELLOW";
System.out.println("绿灯 → 黄灯");
} else if (state.equals("YELLOW")) {
state = "RED";
System.out.println("黄灯 → 红灯");
} else {
throw new IllegalStateException("未知状态: " + state);
}
}
}

​ 通过上面的示例代码可以看出,这段代码存在十分明显的问题:1、状态与行为耦合严重,将所有的逻辑都塞在change()中;2、违反了开闭原则,如果要新增状态必须要在原有代码上修改;3、难以复用,无法为不同状态定义专属行为(比如黄灯时不能启动车辆)。

二、什么是状态机🔍

​ 在计算机科学中,状态机(Finite State Machine, FSM)通常是指有限状态机,它包含有限数量的状态,每一种状态代表系统的一种状况。状态机可以是确定性的(Deterministic Finite State Machine, DFSA),也可以是非确定性的(Nondeterministic Finite State Machine, NFSA)。在确定性状态机中,对于任何给定的输入,都有唯一一个后继状态;而在非确定性状态机中,则可能有多个或没有后继状态。

看学术性解释很复杂对吧,我继续拿上述的红绿灯来举例🌰。红绿灯有三种状态:

  • 红灯(Red)
  • 黄灯(Yellow)
  • 绿灯(Green)

对应的它的行为规则为下列的三种:

  • 红灯 → 绿灯(亮起通行)
  • 绿灯 → 黄灯(准备停止)
  • 黄灯 → 红灯(禁止通行

​ 并且不能跳过状态,在生活你不会看见红灯直接变成黄灯,也不会看见绿灯直接变成红灯(除非故障对吧),这种“状态+触发条件—>新状态”的规则,就是状态机的核心。

顺便在这里介绍一下,状态机的四个基本要素

要素 说明 红绿灯示例
状态(State) 系统当前所处的情形 RED, YELLOW, GREEN
事件(Event) 触发状态转换的信号 “定时器到时”、“手动切换”
转换(Transition) 从一个状态到另一个状态的规则 RED + timeout → GREEN
动作(Action)(可选) 状态切换时执行的操作 “红灯亮起时关闭通行信号”

​ 目前常见的状态机可以分为Moore型和Mealy型,差别在于前者的输出单纯由寄存的状态决定,后者的输出由输入状态输出同时决定。

Moore 型:输出只依赖当前状态(如红灯亮 = 输出“停”)

Mealy 型:输出依赖“当前状态 + 输入事件”(较少用于业务系统)

pZpUvwt.png

小结:状态机并不是什么的算法,而是一种💡建模思想:“状态+触发条件—>新状态”,如果你目前的需要处理的对象,有多个状态(阶段)并且状态直接有严格、明显的顺序规则,可以试试运用状态。

三、实现一个简单的状态机💻

​ 这次用大家都熟悉的订单来做demo吧,先规定一下订单状态流转的应用场景:

1
2
3
CREATED → PAID → SHIPPED → DELIVERED
↓ ↓
CANCELLED CANCELLED
  • 事件:支付(pay)、发货(ship)、确认收货(deliver)、取消(cancel)

  • 约束

    • 只有 CREATED 状态才能支付或取消

    • 只有 PAID 状态才能发货或取消

    • SHIPPED 状态才能确认收货

    • 一旦 CANCELLEDDELIVERED,不能再操作

状态机1.0

笔者学习状态机也是先手搓了一个最简单的状态机,先称为状态机1.0吧。运用最原始的if-else来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 定义状态
enum OrderState {
CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

// 定义事件
enum OrderEvent {
PAY, SHIP, DELIVER, CANCEL
}

// 状态机类
public class OrderStateMachine {
private OrderState currentState = OrderState.CREATED;

public void handleEvent(OrderEvent event) {
switch (currentState) {
case CREATED:
if (event == OrderEvent.PAY) {
currentState = OrderState.PAID;
System.out.println("订单已支付");
} else if (event == OrderEvent.CANCEL) {
currentState = OrderState.CANCELLED;
System.out.println("订单已取消");
} else {
throw new IllegalArgumentException("无效事件: " + event + " 当前状态: " + currentState);
}
break;
case PAID:
if (event == OrderEvent.SHIP) {
currentState = OrderState.SHIPPED;
System.out.println("订单已发货");
} else if (event == OrderEvent.CANCEL) {
currentState = OrderState.CANCELLED;
System.out.println("订单已取消");
} else {
throw new IllegalArgumentException("无效事件: " + event + " 当前状态: " + currentState);
}
break;
// 其他状态处理...
default:
throw new IllegalStateException("未知状态: " + currentState);
}
}

public OrderState getCurrentState() {
return currentState;
}
}

✅优点:简单直观,易于理解。
⚠️缺点:状态和转换逻辑耦合在代码中,复杂系统难以维护。

状态机2.0

​ 随后学习了状态模式(State Pattern),将每个状态封装为一个类,使状态转换更清晰、可扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
interface State {
void handlePay(OrderContext context);
void handleCancel(OrderContext context);
// 其他事件...
}

class CreatedState implements State {
@Override
public void handlePay(OrderContext context) {
System.out.println("订单已支付");
context.setState(new PaidState());
}

@Override
public void handleCancel(OrderContext context) {
System.out.println("订单已取消");
context.setState(new CancelledState());
}
}

// OrderContext 作为上下文持有当前状态
class OrderContext {
private State state;

public OrderContext() {
this.state = new CreatedState();
}

public void setState(State state) {
this.state = state;
}

public void pay() {
state.handlePay(this);
}

public void cancel() {
state.handleCancel(this);
}
}

上述是代码实现结构,下面为完整基于状态模式实现状态机的步骤:

第一步:定义状态枚举(可选,用于日志或外部表示)

1
2
3
4
// OrderStatus.java
public enum OrderStatus {
CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

第二步:定义上下文类(OrderContext)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// OrderContext.java
public class OrderContext {
private OrderState state;
private final String orderId;

public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new CreatedState(); // 初始状态
}

// 对外提供的操作方法
public void pay() {
System.out.println("[" + orderId + "] 触发事件: 支付");
state.onPay(this);
}

public void ship() {
System.out.println("[" + orderId + "] 触发事件: 发货");
state.onShip(this);
}

public void deliver() {
System.out.println("[" + orderId + "] 触发事件: 确认收货");
state.onDeliver(this);
}

public void cancel() {
System.out.println("[" + orderId + "] 触发事件: 取消");
state.onCancel(this);
}

// 供状态类内部切换状态使用
public void setState(OrderState state) {
this.state = state;
System.out.println("[" + orderId + "] 状态变更为: " + state.getStatus());
}

public OrderStatus getCurrentStatus() {
return state.getStatus();
}
}

第三步:定义状态接口

1
2
3
4
5
6
7
8
// OrderState.java
public interface OrderState {
void onPay(OrderContext context);
void onShip(OrderContext context);
void onDeliver(OrderContext context);
void onCancel(OrderContext context);
OrderStatus getStatus();
}

第四步:实现各个具体状态类

1.CreatedState(已创建)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已创建:仅允许支付或取消
public class CreatedState implements OrderState {
@Override
public void onPay(OrderContext context) {
context.setState(new PaidState());
}
@Override
public void onShip(OrderContext context) {
throw new IllegalStateException("订单未支付,不能发货");
}
@Override
public void onDeliver(OrderContext context) {
throw new IllegalStateException("订单未发货,不能确认收货");
}
@Override
public void onCancel(OrderContext context) {
context.setState(new CancelledState());
}
@Override
public OrderStatus getStatus() {
return OrderStatus.CREATED;
}
}
2.PaidState(已支付)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已支付:仅允许发货或取消
public class PaidState implements OrderState {
@Override
public void onPay(OrderContext context) {
throw new IllegalStateException("订单已支付,不能重复支付");
}
@Override
public void onShip(OrderContext context) {
context.setState(new ShippedState());
}
@Override
public void onDeliver(OrderContext context) {
throw new IllegalStateException("订单未发货,不能确认收货");
}
@Override
public void onCancel(OrderContext context) {
context.setState(new CancelledState());
}
@Override
public OrderStatus getStatus() {
return OrderStatus.PAID;
}
}
3. ShippedState(已发货)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已发货:仅允许确认收货
public class ShippedState implements OrderState {
@Override
public void onPay(OrderContext context) {
throw new IllegalStateException("订单已发货,不能支付");
}
@Override
public void onShip(OrderContext context) {
throw new IllegalStateException("订单已发货,不能重复发货");
}
@Override
public void onDeliver(OrderContext context) {
context.setState(new DeliveredState());
}
@Override
public void onCancel(OrderContext context) {
throw new IllegalStateException("订单已发货,不能取消");
}
@Override
public OrderStatus getStatus() {
return OrderStatus.SHIPPED;
}
}
4. DeliveredState(已收货)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已完成:所有操作禁止(终态)
public class DeliveredState implements OrderState {
@Override
public void onPay(OrderContext context) {
throw new IllegalStateException("订单已完成,不能操作");
}
@Override
public void onShip(OrderContext context) {
throw new IllegalStateException("订单已完成,不能操作");
}
@Override
public void onDeliver(OrderContext context) {
throw new IllegalStateException("订单已完成,不能重复确认");
}
@Override
public void onCancel(OrderContext context) {
throw new IllegalStateException("订单已完成,不能取消");
}
@Override
public OrderStatus getStatus() {
return OrderStatus.DELIVERED;
}
}
5. CancelledState(已取消)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已取消:所有操作禁止(终态)
public class CancelledState implements OrderState {
@Override
public void onPay(OrderContext context) {
throw new IllegalStateException("订单已取消,不能操作");
}
@Override
public void onShip(OrderContext context) {
throw new IllegalStateException("订单已取消,不能操作");
}
@Override
public void onDeliver(OrderContext context) {
throw new IllegalStateException("订单已取消,不能操作");
}
@Override
public void onCancel(OrderContext context) {
throw new IllegalStateException("订单已取消,不能重复取消");
}
@Override
public OrderStatus getStatus() {
return OrderStatus.CANCELLED;
}
}

如上所示,每个状态类只定义自己合法的行为,其余操作一律拒绝。

  • CreatedState 专注处理“支付”和“取消”;

  • ShippedState 只响应“确认收货”;

  • 终态(DeliveredState / CancelledState)彻底封闭。
    这种设计让状态逻辑高度内聚、互不干扰,未来即使新增“退款”状态,也无需修改任何现有类

第五步:测试使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Main.java
public class Main {
public static void main(String[] args) {
OrderContext order = new OrderContext("ORDER_123");

System.out.println("当前状态: " + order.getCurrentStatus());

order.pay(); // CREATED → PAID
order.ship(); // PAID → SHIPPED
order.deliver(); // SHIPPED → DELIVERED

// 尝试非法操作
try {
order.cancel(); // 应该抛异常
} catch (IllegalStateException e) {
System.out.println("❌ 操作失败: " + e.getMessage());
}

System.out.println("最终状态: " + order.getCurrentStatus());
}
}
测试结果
1
2
3
4
5
6
7
8
9
10
当前状态: CREATED
[ORDER_123] 触发事件: 支付
[ORDER_123] 状态变更为: PAID
[ORDER_123] 触发事件: 发货
[ORDER_123] 状态变更为: SHIPPED
[ORDER_123] 触发事件: 确认收货
[ORDER_123] 状态变更为: DELIVERED
[ORDER_123] 触发事件: 取消
❌ 操作失败: 订单已完成,不能取消
最终状态: DELIVERED

如何在真实的项目使用?

  • 注入到 Service 层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Service
    public class OrderService {
    public void payOrder(String orderId) {
    OrderContext order = new OrderContext(orderId);
    // 从数据库加载当前状态,设置对应的状态对象(需扩展)
    order.pay();
    // 保存新状态到数据库
    }
    }

优点:符合开闭原则,易于扩展新状态。
⚠️缺点:类数量增多,适合中大型项目

状态机3.0

​ 后续学习发现,SpringBoot提供了原生的状态机,初次之外还有其他第三方的状态机,例如: Squirrel State Machine(GitHub: https://github.com/hekailiang/squirrel)、**Cola State Machine(阿里巴巴开源)**

GitHub: https://github.com/alibaba/COLA)、 Easy State(轻量级)(GitHub: https://github.com/oxo42/easy-state)。

​ 因为时间原因笔者也还没有详细学习,就先简单介绍一下如何使用第三方状态机就以SpringBoot提供的为例,谁让java工程师又名Spring工程师呢😂。

Maven 依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>3.0.0</version> <!-- 请根据 Spring Boot 版本选择 -->
</dependency>

简单配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {

@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.CREATED)
.states(EnumSet.allOf(OrderState.class));
}

@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal().source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
.and()
.withExternal().source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
.and()
.withExternal().source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
}
}

✅优点:功能强大,支持持久化、监听器、嵌套状态等。
⚠️缺点:学习曲线较陡,适合企业级应用

四、总结🧠

​ 通过从红绿灯到订单状态流转的逐步实践,我们看到了状态机如何将混乱的 if-else 逻辑,转化为清晰、安全、可扩展的状态模型。

  • 状态机 1.0(if-else) 虽然简单,但难以维护;
  • 状态机 2.0(状态模式) 通过“行为局部化”,让每个状态只关心自己的职责,既符合开闭原则,又天然防止非法操作;
  • 状态机 3.0(Spring State Machine) 则为复杂企业级场景提供了持久化、监听器、嵌套状态等高级能力。

状态机的本质,不是炫技,而是一种对业务规则的尊重——它强迫我们将隐性的流程显式表达出来,让代码成为业务语言的忠实映射。

当你的对象拥有多个状态、且状态间存在严格流转规则时,不妨试试状态机。它可能不会让你写得更快,但一定会让你改得更安心,错得更少。

好的状态设计,本身就是一道防线。

五、延伸阅读 & 资源推荐📚

如果你对状态机感兴趣,以下文章和项目值得深入学习: