沉默是金,总会发光
大家好,我是沉默
在后台系统开发中,状态流转无处不在:订单从 待支付 → 已支付 → 已发货 ,工单从 打开 → 处理中 → 解决。
一开始用一堆 if/else
、嵌套判断能跑通,但很快会出现:
- 代码像洋葱层层嵌套(箭头代码,难以阅读)
- 状态规则散落在多个分支里,难以维护与测试
- 边界条件容易遗漏,扩展差(新增状态/事件要改核心逻辑)
**
**
所以,状态机(State Machine)诞生了------它是把状态与转移规则显式化、模块化、可测试化的解法。
**-**01-
状态机核心概念
状态机三要素:
要素 | 含义 | 例子(订单系统) |
---|---|---|
状态(State) | 系统所处的稳定阶段 | 待支付 / 已支付 / 已发货 |
事件(Event) | 能触发状态变化的动作 | 支付成功 / 取消订单 / 发货 |
转移(Transition) | 从某个状态在某个事件下到另一个状态并执行处理 | 待支付 + 支付成功 → 已支付(并执行支付逻辑) |
状态机的常见类型:
-
有限状态机(FSM) :最常用、最简单,适合大多数业务场景。
-
分层状态机(HSM) :支持状态继承,减少重复(复杂场景)。
-
状态图 / Statecharts:支持并发子状态、历史状态等高级特性(用于复杂流程)。
- 02-
状态机实战案例
一个清晰且可扩展的状态机实现
vbnet
// TransitionHandler 是一个函数式接口,可以用 lambda 实现@FunctionalInterfacepublic interface TransitionHandler { void handle() throws Exception;}// 状态与事件用 enum 表示public enum State { PENDING, PAID, SHIPPED, CANCELED}public enum Event { PAY_SUCCESS, CANCEL, SHIP}// 转移定义public class Transition { public final State from; public final Event event; public final State to; public final TransitionHandler handler; public Transition(State from, Event event, State to, TransitionHandler handler) { this.from = from; this.event = event; this.to = to; this.handler = handler; }}// 简单状态机实现(遍历转移表)import java.util.ArrayList;import java.util.List;public class StateMachine { private State current; private final List<Transition> transitions = new ArrayList<>(); public StateMachine(State initial) { this.current = initial; } public void addTransition(State from, Event event, State to, TransitionHandler handler) { transitions.add(new Transition(from, event, to, handler)); } public void trigger(Event event) throws Exception { for (Transition t : transitions) { if (t.from == current && t.event == event) { // 执行处理 t.handler.handle(); // 更新状态 current = t.to; return; } } throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持"); } public State getCurrent() { return current; }}
使用示例(main) :
csharp
public static void main(String[] args) throws Exception { StateMachine sm = new StateMachine(State.PENDING); sm.addTransition(State.PENDING, Event.PAY_SUCCESS, State.PAID, () -> { System.out.println("执行支付成功处理逻辑..."); }); sm.addTransition(State.PENDING, Event.CANCEL, State.CANCELED, () -> { System.out.println("执行订单取消逻辑..."); }); sm.addTransition(State.PAID, Event.SHIP, State.SHIPPED, () -> { System.out.println("执行发货逻辑..."); }); System.out.println("当前状态: " + sm.getCurrent()); sm.trigger(Event.PAY_SUCCESS); System.out.println("当前状态: " + sm.getCurrent()); sm.trigger(Event.SHIP); System.out.println("当前状态: " + sm.getCurrent());}
输出:
makefile
当前状态: 待支付执行支付成功处理逻辑...当前状态: 已支付执行发货逻辑...当前状态: 已发货
优化:表驱动实现(查找 O(1))
原实现触发时遍历转移表,性能在大量状态/事件时受影响。把转移表做成 map[State]map[Event]*Transition
,触发变成两次 map 查找,既直观又高效:
csharp
import java.util.HashMap;import java.util.Map;public class StateMachineV2 { private State current; private Map<State, Map<Event, Transition>> transitionMap; public StateMachineV2(State initial) { this.current = initial; this.transitionMap = new HashMap<>(); } public void addTransition(State from, Event event, State to, TransitionHandler handler) { transitionMap .computeIfAbsent(from, k -> new HashMap<>()) .put(event, new Transition(from, event, to, handler)); } public void trigger(Event event) throws Exception { Map<Event, Transition> events = transitionMap.get(current); if (events != null) { Transition t = events.get(event); if (t != null) { t.handler.handle(); current = t.to; return; } } throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持"); } public State getCurrent() { return current; }}
面向对象风格:状态模式
用接口把每个状态封装成一个对象,状态对象实现该状态下能做的操作。好处是逻辑分散到各自状态类,遵循单一职责;坏处是类型和样板代码增多。
java
public interface OrderState { void pay(OrderContext ctx) throws Exception; void cancel(OrderContext ctx) throws Exception; void ship(OrderContext ctx) throws Exception;}public class PendingState implements OrderState { @Override public void pay(OrderContext ctx) { System.out.println("Pending -> 支付处理"); ctx.setState(new PaidState()); } @Override public void cancel(OrderContext ctx) { System.out.println("Pending -> 取消订单"); ctx.setState(new CanceledState()); } @Override public void ship(OrderContext ctx) { throw new IllegalStateException("当前状态不能发货"); }}public class PaidState implements OrderState { @Override public void pay(OrderContext ctx) { throw new IllegalStateException("已支付,无法重复支付"); } @Override public void cancel(OrderContext ctx) { System.out.println("Paid -> 已支付取消逻辑"); ctx.setState(new CanceledState()); } @Override public void ship(OrderContext ctx) { System.out.println("Paid -> 发货处理"); ctx.setState(new ShippedState()); }}// 其余状态类略...public class OrderContext { private OrderState state; public OrderContext(OrderState state) { this.state = state; } public void setState(OrderState state) { this.state = state; } public String getStateName() { return state.getClass().getSimpleName(); } public void pay() throws Exception { state.pay(this); } public void cancel() throws Exception { state.cancel(this); } public void ship() throws Exception { state.ship(this); }}
并发与安全:
在并发场景下,必须保证状态转移是原子且幂等的:
- 简单做法:在
Trigger
外包一层mutex.Lock()
/Unlock()
。 - 更复杂:使用 DB 乐观锁(version)或分布式锁(Redis、Zookeeper)保证跨进程一致性。
- 异步场景:用消息队列序列化事件处理,保证顺序性。
java
public class SafeStateMachineV2 extends StateMachineV2 { private final Object lock = new Object(); public SafeStateMachineV2(State initial) { super(initial); } @Override public void trigger(Event event) throws Exception { synchronized (lock) { super.trigger(event); } }}
持久化:
数据库里只记 order.status
、事件历史(audit trail)与时间戳。代码在恢复时:从 DB 读回当前状态并注入状态机即可重建运行时行为(状态机逻辑不存入 DB)。
sql
// 存储示例(伪代码)orders( id BIGINT PRIMARY KEY, status VARCHAR(32), event_history JSON, version BIGINT, updated_at TIMESTAMP)// 恢复:Order order = loadOrderFromDB(id);StateMachineV2 sm = CreatePreconfiguredStateMachine(order.getStatusEnum());
持久化事务注意:
-
在触发转移时,应先执行业务逻辑(可能跨系统),再持久化状态与审计(尽量保证幂等)。
-
使用事务 + 幂等设计(或事务外补偿)保证一致性。
- 03-
反模式与实践建议
别犯这些坑:
-
过度复杂:状态超过 ~15 个,考虑拆分多个子状态机。
-
上帝状态机:一个状态机控制太多业务,职责混乱。
-
忽略回退:关键流程必须设计补偿/回退逻辑(尤其是跨系统流程)。
-
缺乏监控:必须记录状态转移日志与失败告警。
监控示例(埋点):
在 trigger
前后记录日志、耗时、失败信息,监控异常率、非法事件请求。
csharp
public void triggerWithMonitoring(Event event) throws Exception { long start = System.currentTimeMillis(); State old = getCurrent(); try { trigger(event); } finally { System.out.printf("state transition: %s -> %s on %s, took %d ms%n", old, getCurrent(), event, System.currentTimeMillis() - start); }}
**-****04-**总结
通过本文,你应该能做到:
-
理解状态 / 事件 / 转移三要素
-
会写一个简单可扩展的 Java 状态机
-
知道如何做性能优化(表驱动)与并发保护
-
理解持久化原则:只存状态,记录历史
-
避免常见反模式,做好监控与可恢复设计
状态机不仅是代码结构,更是工程思维
**-****05-**粉丝福利
r
我这里创建一个程序员成长&副业交流群,
和一群志同道合的小伙伴,一起聚焦自身发展,
可以聊:
技术成长与职业规划,分享路线图、面试经验和效率工具,
探讨多种副业变现路径,从写作课程到私活接单,
主题活动、打卡挑战和项目组队,让志同道合的伙伴互帮互助、共同进步。
如果你对这个特别的群,感兴趣的,
可以加一下, 微信通过后会拉你入群,
但是任何人在群里打任何广告,都会被我T掉。