关于状态机——用事件触发状态 一、为什么要学习状态机?🤔 在最近的学习与实践过程中,笔者频繁地遇到需要设置并变更对象状态的情形,这一过程促使我去探索更为系统化的解决方案——状态机。为了更好地引入这个概念,让我们从日常生活中最常见的例子说起:红绿灯。就像红绿灯明确指示着车辆何时停止、何时行进一样,状态机也为我们提供了一套清晰且有序的框架,用以管理和转换软件系统中复杂的状态逻辑。通过学习和应用状态机,我们不仅能够有效避免代码中的混乱与冗余,还能大幅提升系统的可维护性和扩展性。
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" ; 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 型 :输出依赖“当前状态 + 输入事件”(较少用于业务系统)
小结:状态机并不是什么的算法,而是一种💡建模思想:“状态+触发条件—>新状态 ”,如果你目前的需要处理的对象,有多个状态(阶段)并且状态直接有严格、明显的顺序规则,可以试试运用状态。
三、实现一个简单的状态机💻 这次用大家都熟悉的订单来做demo吧,先规定一下订单状态流转的应用场景:
1 2 3 CREATED → PAID → SHIPPED → DELIVERED ↓ ↓ CANCELLED CANCELLED
状态机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 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 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 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; } }
如上所示,每个状态类只定义自己合法的行为 ,其余操作一律拒绝。
第五步:测试使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Main { public static void main (String[] args) { OrderContext order = new OrderContext ("ORDER_123" ); System.out.println("当前状态: " + order.getCurrentStatus()); order.pay(); order.ship(); order.deliver(); 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) 则为复杂企业级场景提供了持久化、监听器、嵌套状态等高级能力。
状态机的本质,不是炫技,而是一种对业务规则的尊重 ——它强迫我们将隐性的流程显式表达出来,让代码成为业务语言的忠实映射。
当你的对象拥有多个状态、且状态间存在严格流转规则时,不妨试试状态机。它可能不会让你写得更快,但一定会让你改得更安心,错得更少。
好的状态设计,本身就是一道防线。
五、延伸阅读 & 资源推荐📚 如果你对状态机感兴趣,以下文章和项目值得深入学习:
Author:
frank
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE