枚举类实现的有限状态机核心是管控状态流转而非存储状态,需明确定义各状态可响应的事件及转移目标,禁止持有业务数据、避免ordinal判断、解耦状态变化与变量更新,并通过外部服务执行具体动作。

枚举类实现的有限状态机,核心不是“存状态”,而是“管流转”——它不负责持有业务数据,但必须清晰定义:当前状态能响应哪些事件、响应后变成什么状态、是否允许回退或跳转。变量流转的关键,在于把“状态变化”和“变量更新”解耦,让枚举只回答“能不能变”,具体“怎么变数据”交给外部服务处理。
状态定义要封闭且自解释
枚举值必须覆盖全部合法状态,一个都不能少,也不能用字符串或数字代替。比如订单状态不能只写 CREATED, PAID, SHIPPED,漏掉 CANCELLED 或 REFUNDED,后续新增逻辑时就容易误判。
- 每个常量显式声明自己支持的转移目标,例如 PAID 允许转到 SHIPPED 和 REFUNDED,但不允许回到 CREATED
- 终态(如 DELIVERED、CANCELLED)不重写转移方法,调用即抛 AbstractMethodError,比返回 null 更早暴露误操作
- 避免用 ordinal() 判断逻辑分支——一旦调整枚举顺序,所有基于序号的判断全失效,且编译器完全不提醒
流转逻辑必须绑定事件,而非硬编码状态对
不要写 if (from == CREATED && to == PAID) 这类双参数校验。正确做法是让每个状态自己决定“收到某事件后去哪”:
- 定义统一事件枚举 Event { PAY, SHIP, CANCEL, REFUND }
- 每个状态重写 transition(Event e) 方法,只处理自己能响应的事件
- 非法事件直接抛异常,不静默忽略、不返回自身、不 fallback 到默认值
- 例如 CREATED.transition(Event.SHIP) 应该失败,而不是悄悄返回 CREATED 导致流程卡死
变量更新必须由调用方驱动,枚举绝不持有可变字段
枚举实例是 JVM 全局单例,如果在 OrderStatus 里加一个 private int retryCount,所有订单共享这个计数器——这是高频误用。
- 上下文数据(如支付渠道、取消原因、审批人ID)必须作为参数传入 transition() 或对应行为方法
- 真正修改业务变量(如更新数据库字段、发消息、扣库存)的操作,放在独立的服务类中,根据 fromState.transition(e) 的返回结果触发
- 可以在枚举中声明函数式接口字段(如 Runnable onEnter),但只做声明,执行时机由外部控制,避免副作用不可控
配合外部服务完成完整流转闭环
枚举只做决策,不做执行。一次完整变量流转应分三步走:
- 调用 currentState.transition(event) 得到目标状态,若抛异常则拒绝本次操作
- 检查目标状态是否与预期一致(如支付成功必须进入 PAID,否则告警)
- 由服务类根据 from → to 组合执行对应动作:比如从 PAID → SHIPPED 时生成运单、通知物流;从 CREATED → CANCELLED 时释放预占库存
不复杂但容易忽略


















