状态模式需分离状态行为、转换规则与上下文职责,状态类应轻量单例、不持业务数据,转换逻辑预构建映射表,耗时操作解耦为事件驱动,超50状态或复杂条件时应引入dsl或状态机引擎。
状态模式不是给状态“起个名字”就完事,而是把状态的行为、转换规则和上下文职责真正拆开。它解决的核心问题是:当对象行为随状态剧烈变化,且状态数增长后,if-else 会失控、校验逻辑重复、新增状态要改多处——这时候,状态模式才真正显出价值。
状态类必须轻量,别让它记住业务数据
每个具体状态类(比如 PaidState、ShippedState)不该持有订单ID、用户信息、时间戳等运行时变量。这些属于上下文(OrderContext)的职责,应由外部传入方法参数中。
- ✅ 正确做法:状态类是单例或静态常量,全局复用。例如
PaidState.INSTANCE - ❌ 错误做法:每次状态切换都
new PaidState(orderId, userId)—— 百万订单就会生成百万对象,GC 压力陡增 - 状态类只做两件事:判断当前是否允许该操作 + 决定下一个状态是谁
状态转换不能靠运行时 if 或反射
高频流转场景下,每步都走 if (state == PAID && event == SHIP) 或用 Class.forName() 加载状态类,性能会断崖式下跌。
- 系统启动时预构建转换图:
Map<state map state>></state>,用EnumMap+EnumSet提升查找效率 - 固定流程(如支付→发货→签收)可抽象为「状态链」,跳过中间校验,直接批量推进
- 把风控检查这类耗时动作设计为 Transition Hook,不作为独立状态节点,避免状态爆炸
状态变更与副作用必须解耦
状态切换本身应控制在微秒级。发消息、更新数据库、调第三方接口这些耗时操作,绝不能放在 setState() 或状态类的 handle 方法里。
- 核心逻辑只做:校验合法性 + 更新
context.currentState - 后续动作通过发布领域事件(如
OrderPaidEvent)触发异步消费者处理 - 这样既保证吞吐,又支持失败重试、幂等写入和事件溯源,对百万级流程至关重要
什么时候该换工具,而不是硬撑?
当状态超过 50 个、事件类型超 20 种,或转换条件含金额、角色、时间等动态表达式时,纯面向对象的状态模式会迅速变成维护噩梦。
- 考虑引入 DSL 描述状态流转(如 YAML 定义转换规则)
- 评估 Spring StateMachine 或自研轻量引擎,用状态图可视化代替代码堆砌
- 接受“部分非 OOP”:状态定义交给配置,行为实现仍用 Java,兼顾灵活性与可读性










