Java 状态机设计:替代 if-else 的优雅架构
沉默是金,总会发光
大家好,我是沉默
在后台系统开发中,状态流转无处不在:订单从 待支付 → 已支付 → 已发货,工单从 打开 → 处理中 → 解决。
一开始用一堆 if/else、嵌套判断能跑通,但很快会出现:
-
代码像洋葱层层嵌套(箭头代码,难以阅读)
-
状态规则散落在多个分支里,难以维护与测试
-
边界条件容易遗漏,扩展差(新增状态/事件要改核心逻辑)
所以,状态机(State Machine)诞生了——它是把状态与转移规则显式化、模块化、可测试化的解法。
-01-
状态机核心概念
状态机三要素:
|
要素 |
含义 |
例子(订单系统) |
|---|---|---|
| 状态(State) |
系统所处的稳定阶段 |
待支付 / 已支付 / 已发货 |
| 事件(Event) |
能触发状态变化的动作 |
支付成功 / 取消订单 / 发货 |
| 转移(Transition) |
从某个状态在某个事件下到另一个状态并执行处理 |
待支付 + 支付成功 → 已支付(并执行支付逻辑) |
状态机的常见类型:
-
有限状态机(FSM):最常用、最简单,适合大多数业务场景。
-
分层状态机(HSM):支持状态继承,减少重复(复杂场景)。
-
状态图 / Statecharts:支持并发子状态、历史状态等高级特性(用于复杂流程)。
-02-
状态机实战案例
一个清晰且可扩展的状态机实现
// 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):
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());}
输出:
当前状态: 待支付执行支付成功处理逻辑...当前状态: 已支付执行发货逻辑...当前状态: 已发货
优化:表驱动实现(查找 O(1))
原实现触发时遍历转移表,性能在大量状态/事件时受影响。把转移表做成 map[State]map[Event]*Transition,触发变成两次 map 查找,既直观又高效:
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;}}
面向对象风格:状态模式
用接口把每个状态封装成一个对象,状态对象实现该状态下能做的操作。好处是逻辑分散到各自状态类,遵循单一职责;坏处是类型和样板代码增多。
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 {@Overridepublic void pay(OrderContext ctx) {System.out.println("Pending -> 支付处理");ctx.setState(new PaidState());}@Overridepublic void cancel(OrderContext ctx) {System.out.println("Pending -> 取消订单");ctx.setState(new CanceledState());}@Overridepublic void ship(OrderContext ctx) {throw new IllegalStateException("当前状态不能发货");}}public class PaidState implements OrderState {@Overridepublic void pay(OrderContext ctx) {throw new IllegalStateException("已支付,无法重复支付");}@Overridepublic void cancel(OrderContext ctx) {System.out.println("Paid -> 已支付取消逻辑");ctx.setState(new CanceledState());}@Overridepublic 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)保证跨进程一致性。
-
异步场景:用消息队列序列化事件处理,保证顺序性。
public class SafeStateMachineV2 extends StateMachineV2 {private final Object lock = new Object();public SafeStateMachineV2(State initial) { super(initial); }@Overridepublic void trigger(Event event) throws Exception {synchronized (lock) {super.trigger(event);}}}
持久化:
数据库里只记 order.status、事件历史(audit trail)与时间戳。代码在恢复时:从 DB 读回当前状态并注入状态机即可重建运行时行为(状态机逻辑不存入 DB)。
// 存储示例(伪代码)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 前后记录日志、耗时、失败信息,监控异常率、非法事件请求。
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-
粉丝福利
我这里创建一个程序员成长&副业交流群,
和一群志同道合的小伙伴,一起聚焦自身发展,
可以聊:
技术成长与职业规划,分享路线图、面试经验和效率工具,
探讨多种副业变现路径,从写作课程到私活接单,
主题活动、打卡挑战和项目组队,让志同道合的伙伴互帮互助、共同进步。
如果你对这个特别的群,感兴趣的,
可以加一下,微信通过后会拉你入群,
但是任何人在群里打任何广告,都会被我T掉。
更多推荐


所有评论(0)