状态模式核心是委托而非继承,应通过接口实现状态切换、守卫逻辑管控迁移、依场景权衡是否使用。

状态模式的核心不是继承,是委托
Java里写状态模式最容易掉进的坑,是把 State 抽成抽象类,再让每个具体状态去继承——结果发现切换状态时要反复 new 实例、父类里塞一堆空实现、context 里堆满 if-else 判断该 new 哪个子类。这不是状态模式,这是状态枚举的拙劣模仿。
真正轻量又可控的做法:所有具体状态实现同一个接口,Context 持有该接口的引用,运行时直接替换引用对象。切换状态就是赋值,不涉及构造开销,也不用反射或工厂兜底。
-
State接口只定义行为方法,不定义任何字段或默认逻辑 - 每个具体状态类(如
LockedState、UnlockedState)是无状态的纯行为载体,可复用、可单例化 -
Context的状态变更方法(如setState(State newState))应校验传入非 null,避免 NPE
如何避免状态流转失控?加一层守卫逻辑
真实业务里,不是所有状态都能任意跳转。比如门不能从 LockedState 直接切到 BrokenState,必须先经过 RepairingState。如果只靠外部调用 setState(),很容易在多线程或复杂调用链中绕过约束。
解决方案是把状态迁移规则收归 Context 内部,每个「触发动作」对应一个守卫方法,而不是暴露原始 setState:
立即学习“Java免费学习笔记(深入)”;
- 提供
lock()、unlock()、breakDown()等语义化方法 - 每个方法内部判断当前状态是否允许执行,再决定调用哪个
State的行为,并主动更新自身状态 - 禁止外部直接调用
setState(),把它设为protected或包私有
这样既守住状态合法性,又让调用方不用记「现在是什么状态才能调什么」。
什么时候不该用状态模式?看变更频率和分支深度
如果对象只有两个状态(如开/关),且行为差异只是几行 if 分支,硬套状态模式反而增加理解成本和类数量。JVM 加载多个 State 实现类也有微小开销。
适合上状态模式的信号很明确:
- 状态数 ≥ 4,且新增状态频繁(比如支付流程:
Created→Paid→Shipped→Delivered→Refunded) - 同一操作在不同状态下行为差异大(比如
handleEvent()在IdleState是丢弃,在ProcessingState是入队,在ErrorState是告警+重试) - 状态之间存在明确迁移约束,且约束逻辑会随业务演进变化
否则,用 enum + switch 更直白,Java 14+ 的 switch 表达式还能返回值,足够干净。
Java 8+ 可以用函数式简化,但别滥用
如果状态行为极其简单(比如只是改一个字段、打一行日志),可以用 Consumer<context></context> 或 Supplier<state></state> 替代接口实现。但要注意:
- lambda 无法序列化,如果
Context要存 Redis 或跨 JVM 传递,得退回到具名类 - 调试时 lambda 堆栈信息不友好,
State类名能一眼看出当前行为意图 - 多个 lambda 共享变量容易引发闭包陷阱,比如在循环里创建状态并捕获循环变量
所以建议:初期验证逻辑用 lambda 快速跑通;一旦行为变复杂、要加日志、要测覆盖率、要上线,立刻拆成独立 State 类。别等出问题了再重构。
状态模式真正的难点不在结构,而在厘清「谁有权触发迁移」「迁移失败时要不要回滚上下文」「并发修改状态时怎么保证一致性」——这些从来不会出现在 UML 图里,但天天在日志里报错。







