走进 Gang of Four 设计模式:状态模式
说明:不仅告诉你"怎么用",更告诉你"为什么这样设计"
前置知识:Java 面向对象基础(接口、多态)、有限状态机(FSM)基本概念
📊 设计模式分类总览
GoF 23 种设计模式可按两个维度交叉分类:类/对象(处理方式) × 创建型/结构型/行为型(目的)。
| 类(Class)(通过继承复用) | Factory Method工厂方法 | Adapter(类适配器) | Interpreter(解释器)Template Method(模板方法) |
| 对象(Object)(通过组合/聚合复用) | Abstract Factory(抽象工厂)Builder(建造者)Prototype(原型)Singleton(单例) | Adapter(对象适配器)Bridge(桥接)Composite(组合)Decorator(装饰)Facade(外观)Flyweight(享元)Proxy(代理) | Chain of Resp.(责任链)Command(命令)Iterator(迭代器)Mediator(中介者)Memento(备忘录)Observer(观察者)State(状态)Strategy(策略)Visitor(访问者) |
📑 目录
- Tomcat Lifecycle 源码拆解
- JSF Lifecycle 源码拆解
- Spring State Machine 源码拆解
1. 模式概述 · 15 问深度分析
Q1: 为什么需要这个模式?它解决了什么问题?
在软件开发中,很多对象都具有内部状态,且对象的行为彻底依赖于它当前所处的状态。例如:
- TCP 连接(Listen, Established, Closed)
- 订单状态(待支付、已支付、已发货、已签收)
- 自动售货机(无硬币、有硬币、售出中、售罄)
状态模式的核心目的就是解决对象状态转换以及不同状态下行为差异的管理问题。它将"某种状态下的特定行为"从庞大的宿主类中抽离出来,封装到独立的状态类中。它解决了行为随状态改变而改变时的复杂度和耦合度问题,避免了对象的核心业务逻辑被淹没在无穷无尽的状态判断中。
Q2: 如果不用这个模式,会有什么缺陷?
如果不使用状态模式,最直接的做法是在一个巨型方法中使用大量的 if-else 或 switch-case 语句来显式判断当前状态,并根据状态执行不同的逻辑。
// ❌ 大量 if-else,新增状态需要改所有方法
public void cancel() {
if (state == "PENDING") {
// 待支付:直接取消
state = "CANCELED";
} else if (state == "PAID") {
// 已支付:取消并退款
refund();
state = "CANCELED";
} else if (state == "SHIPPED") {
// 已发货:需联系物流拦截
logistics.intercept();
state = "CANCELED";
}
// 新增 REFUNDED 状态需要在这里加新分支
}
缺陷:
- 违背开闭原则(OCP):只要增加或修改一个状态,就必须深入修改包含状态判断的核心业务代码,极易引入Bug。
- 代码臃肿与可读性差:当状态较多(比如 >5 个)且每个状态下的行为很复杂时,代码会变得极其冗长。
- 状态转换逻辑混乱:状态的跳转控制交织在各个条件分支中,没有一个清晰的全局视图,很难跟踪状态是在哪里被改变的。
- 复用性差:相同状态下的行为逻辑无法在其他类似上下文中复用。
Q3: 核心思想是什么?一句话如何概括?
核心思想:将状态从对象中剥离,将"状态判断"转化为"多态对象调用",把每种状态下的行为和状态流转封装到独立的对象中。
一句话概括:状态模式通过将状态封装为独立的类,把复杂的条件分支判断(if-else)转化为面向对象的多态行为调用。
Q4: 包含哪些角色?每个角色的职责是什么?
状态模式主要包含以下三个核心角色:
| Context(环境上下文) | 定义客户端需要的接口,维护一个当前状态的实例(State)。负责将所有与状态相关的请求委托给当前的 State 对象处理,并提供修改当前状态的方法。 |
| State(抽象状态) | 定义一个接口或抽象类,用以封装与 Context 的一个特定状态相关的行为契约。这个接口声明了所有可能受状态影响的方法。 |
| ConcreteState(具体状态) | 每一个类代表 Context 的一种具体状态。它实现了 State 接口,负责实现该状态下对应的特定行为逻辑,并在需要时触发 Context 的状态流转。 |
Q5: 它们之间如何协作?调用流程是怎样的?
协作机制:
调用流程:
Client -> Context.request()
│
└─> CurrentState.handle(Context)
│
├─ [执行该状态特有的业务逻辑]
│
└─> Context.setState(NextState) // 状态发生流转
Q6: 为什么要这样设计?每个角色存在的意义是什么?
- Context 的存在意义:它是对外的统一窗口。客户端不需要知道内部有多少个状态、状态怎么切、当前是谁。这保护了系统内部的封装性。
- State 的存在意义:建立统一的行为规范(契约)。它告诉所有具体状态:无论你处于什么状态,你都必须对这一组外部事件(行为)做出响应。
- ConcreteState 的存在意义:彻底消除 if-else 的根源。每个状态类只专注一件事情——“我在这个状态下应该做什么,下一步去哪”。这种单一职责使得代码逻辑极度内聚。
Q7: 为什么使用接口、抽象类、组合,而不是其他方式?
状态模式精妙地融合了面向对象的三大核心工具:
- 为什么用接口/抽象类:为了利用多态(Polymorphism)。多态是消除 if-else 的标准解法。Context 只需要持有一个抽象的 State 引用,在运行时动态绑定具体的实现,从而在不修改 Context 代码的前提下改变行为。
- 为什么用组合(Composition):Context"拥有"一个 State(Has-A 关系),而不是"继承"自 State(Is-A 关系)。如果用继承,一个对象在运行时是无法动态改变自己的父类的;而用组合,Context 可以在运行时随时替换掉自己维护的 State 实例,从而完美实现了状态的动态切换。
Q8: 这种设计符合哪些面向对象原则?
- 开闭原则(OCP):极为推崇。当需要增加新的状态时,只需要新增一个 ConcreteState 类即可,完全不需要修改 Context 或是其他状态类的代码(除非状态流转强耦合)。
- 单一职责原则(SRP):每个 ConcreteState 只负责处理自己对应状态下的行为和流转,逻辑单一,责任明确。而 Context 只负责维护状态的上下文和委托,不掺杂具体业务。
- 依赖倒置原则(DIP):Context 依赖于抽象的 State 接口,而不是具体的 ConcreteState 类。高层模块和底层具体状态都向抽象靠拢。
Q9: 它有哪些优点和局限性?会带来哪些额外成本?
优点:
- 消除了臃肿的条件分支语句,提升了代码的可读性和可维护性。
- 显式化状态转换:将状态流转逻辑分布在各个状态类中,使得状态机的转变非常清晰。
- 优秀的扩展性:增加新状态非常容易。
局限性与额外成本:
- 类膨胀(Class Explosion):每一个状态都需要一个独立的类。如果一个业务有 20 个状态,就会多出 20 个类。
- 结构复杂化:相比直接写 if-else,引入了更多的抽象层和间接调用,初学者理解成本较高。
- 流转逻辑分散:如果状态流转由 ConcreteState 控制,那么状态机的全局跳转逻辑会分散在各个状态类中,无法在单一地方一览全貌。
Q10: 它最适合哪些场景?哪些场景不应该使用?
最适合的场景:
- 对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 代码中包含大量与对象状态相关的条件分支语句(if-else 或 switch-case),且这些分支里包含了复杂的业务逻辑。
- 状态本身具有稳定的生命周期和清晰的流转逻辑(如工作流、审批流、游戏人物状态机、TCP 协议栈等)。
不应该使用的场景:
- 状态极少且极其稳定(例如只有 2 个状态:启用/禁用),且状态下的行为极其简单。此时直接用布尔值或简单枚举效率更高,用状态模式属于过度设计。
- 状态之间没有行为差异,只是属性值不同。这种情况下应该建模为属性数据,而不是状态模式。
Q11: 它与容易混淆的其他设计模式有什么区别和联系?
状态模式 vs 策略模式(Strategy Pattern):
- 结构极度相似:它们的类图几乎完全一样(Context 持有一个抽象接口,有多个具体实现)。
- 意图完全不同:
- 策略模式是让客户端显式选择一种算法或行为(如选择支付宝还是微信支付),各个策略之间是平行的、独立的、互斥的,通常没有相互转换的概念。
- 状态模式是让对象的行为随着内部状态的改变而自动改变,各个具体状态之间是紧密相连的(包含状态的流转逻辑),客户端通常不感知状态的切换。
状态模式 vs 责任链模式(Chain of Responsibility):
- 责任链模式是将请求沿着一条预设好的链条传递,直到有对象处理它为止。
- 状态模式是 Context 直接精准命中当前的 State 实例并执行,State 内部知道下一个状态是谁,并进行显式替换。
Q12: 框架中有哪些典型应用?
- Spring State Machine:Spring 官方提供的独立子项目,是状态模式在企业级应用中的集大成者,专门用于解决复杂的分布式状态机和工作流控制。
- JSF (JavaServer Faces):其生命周期管理(Lifecycle)在处理请求的不同阶段(Restore View, Apply Request Values 等)时,采用了类似状态模式的机制。
- Tomcat 中的 Lifecycle 管理:Tomcat 的组件(如 Container, Connector)有明确的生命周期状态(NEW, INITIALIZING, INITIALIZED, STARTING_PREP, STARTED…)。
Q13: 如何从零实现一个最小可运行版本?
以"马里奥的状态变化"(小马里奥 -> 超级马里奥 -> 斗篷马里奥 -> 死亡)为例:
// 1. 抽象状态接口
public interface MarioState {
void obtainMushroom(MarioStateMachine context);
void obtainCape(MarioStateMachine context);
void meetMonster(MarioStateMachine context);
String getName();
}
// 2. 具体状态实现
public class SmallMarioState implements MarioState {
@Override
public void obtainMushroom(MarioStateMachine context) {
System.out.println("吃蘑菇 -> 变成超级马里奥!");
context.setCurrentState(new SuperMarioState());
}
@Override
public void obtainCape(MarioStateMachine context) {
System.out.println("拿斗篷 -> 变成斗篷马里奥!");
context.setCurrentState(new CapeMarioState());
}
@Override
public void meetMonster(MarioStateMachine context) {
System.out.println("遇到怪物 -> 挂了!");
context.setCurrentState(new DeadState());
}
@Override public String getName() { return "小马里奥"; }
}
public class SuperMarioState implements MarioState {
@Override
public void obtainMushroom(MarioStateMachine context) { /* 已是超级,无变化 */ }
@Override
public void obtainCape(MarioStateMachine context) {
System.out.println("拿斗篷 -> 变成斗篷马里奥!");
context.setCurrentState(new CapeMarioState());
}
@Override
public void meetMonster(MarioStateMachine context) {
System.out.println("遇到怪物 -> 退化成小马里奥!");
context.setCurrentState(new SmallMarioState());
}
@Override public String getName() { return "超级马里奥"; }
}
public class CapeMarioState implements MarioState {
@Override
public void obtainMushroom(MarioStateMachine context) {}
@Override
public void obtainCape(MarioStateMachine context) {}
@Override
public void meetMonster(MarioStateMachine context) {
System.out.println("遇到怪物 -> 退化成小马里奥!");
context.setCurrentState(new SmallMarioState());
}
@Override public String getName() { return "斗篷马里奥"; }
}
public class DeadState implements MarioState {
@Override public void obtainMushroom(MarioStateMachine context) {}
@Override public void obtainCape(MarioStateMachine context) {}
@Override public void meetMonster(MarioStateMachine context) {}
@Override public String getName() { return "死亡"; }
}
// 3. Context
public class MarioStateMachine {
private MarioState currentState;
public MarioStateMachine() {
this.currentState = new SmallMarioState();
}
public void setCurrentState(MarioState state) {
this.currentState = state;
}
public void obtainMushroom() { currentState.obtainMushroom(this); }
public void obtainCape() { currentState.obtainCape(this); }
public void meetMonster() { currentState.meetMonster(this); }
public void showStatus() {
System.out.println("当前状态: " + currentState.getName());
}
}
// 4. 运行
public class Main {
public static void main(String[] args) {
MarioStateMachine mario = new MarioStateMachine();
mario.showStatus(); // 小马里奥
mario.obtainMushroom(); mario.showStatus(); // 超级马里奥
mario.obtainCape(); mario.showStatus(); // 斗篷马里奥
mario.meetMonster(); mario.showStatus(); // 小马里奥
}
}
Q14: 如何在现有项目中识别可以使用该模式的地方?
public void updateOrder() {
if (status == 1) { ... }
else if (status == 2) { ... }
// 超过3层以上的状态 if-else 嵌套
}
Q15: 如果重新设计这个模式,在今天会有哪些改进?
A. 函数式编程与声明式状态机(消灭类膨胀)
利用 Java 8+ 的 Lambda + Enum,可以在一处声明完状态的行为矩阵:
public enum OrderState {
CREATED {
@Override public void handle(OrderContext ctx) {
/* 支付逻辑 */ ctx.setState(PAID);
}
},
PAID {
@Override public void handle(OrderContext ctx) {
/* 发货逻辑 */ ctx.setState(SHIPPED);
}
},
SHIPPED {
@Override public void handle(OrderContext ctx) {
/* 签收逻辑 */ ctx.setState(FINISHED);
}
};
public abstract void handle(OrderContext ctx);
}
B. 结合依赖注入(Spring IoC)管理无状态状态类
状态对象可以设计为无状态的单例 Bean(Singleton),流转时通过 IoC 容器获取对应 Bean:
@Component
public class PaidState implements State {
@Autowired private ShippingService shippingService;
@Override
public void doAction(Context ctx) {
shippingService.ship();
ctx.setState(SpringContextHolder.getBean(ShippedState.class));
}
}
C. 微服务与分布式环境下的状态持久化
Context 变为纯粹的 DTO,当前状态完全持久化在数据库或 Redis 中。每次事件触发时:从 Redis/DB 读取当前状态 → 匹配对应的 State 处理器 → 驱动行为并将最新状态写回数据库。
2. 框架源码实战分析
状态模式思想在面对不同的工业级场景时,演进出了三种不同的实现方向:从最标准的工业级状态机实现(Spring State Machine),到经典的管道流式状态机(JSF Lifecycle),再到高度简化的"枚举+骨架实现"变种(Tomcat Lifecycle)。
2.1 Tomcat Lifecycle 源码拆解
Tomcat 的状态机并没有为每个状态独立创建类(如果创建,20 多个组件乘以十几种状态会导致类爆炸),而是将状态内聚在 LifecycleState 枚举中,并通过一个抽象骨架类 LifecycleBase 来统一驱动状态的扭转与事件通知。
角色映射
| State | LifecycleState 枚举 |
| Context | LifecycleBase 抽象类 |
| ConcreteState | 枚举常量 + LifecycleBase 内置的状态校验 |
| Client | StandardContext、StandardServer 等组件 |
核心源码逐步剖析
1. LifecycleState 枚举——定义了组件的所有可能状态:
public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null);
// …
}
2. LifecycleBase.init()——定义了标准的状态判定与委托流程:
public abstract class LifecycleBase implements Lifecycle {
private volatile LifecycleState state = LifecycleState.NEW;
@Override
public final synchronized void init() throws LifecycleException {
// 状态校验:如果不等于 NEW,说明重复初始化或状态错误
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 状态流转:执行初始化前,将状态变更为 INITIALIZING
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 核心委托:调用子类复写的具体初始化业务逻辑(模板方法模式)
initInternal();
// 状态流转:执行成功后,将状态变更为 INITIALIZED
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.initFail", toString()), t);
}
}
// 留给具体组件实现的业务钩子
protected abstract void initInternal() throws LifecycleException;
}
3. setStateInternal()——触发状态变更并广播事件(结合观察者模式):
private synchronized void setStateInternal(LifecycleState state, Object data, boolean check) {
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent, data); // 通知监听器
}
}
Tomcat 设计点评:这是状态模式在低频、高内聚生命周期管理中的典型应用。它不盲目追求"一个状态一个类",而是通过模板方法模式 + 枚举控制,在保证 Context 线程安全(通过 synchronized 和 volatile)的前提下,完美消除了子类组件对状态逻辑的硬编码。
2.2 JSF Lifecycle 源码拆解
JSF 是 Java EE 规范中的一个基于组件的 Web 框架,其核心是一个复杂的请求处理生命周期(包含 6 个核心阶段)。JSF 将每一个阶段(状态)都抽象成独立的 Phase 类,由控制中心统一调度。
角色映射
| Context | javax.faces.lifecycle.Lifecycle |
| State | javax.faces.lifecycle.Phase |
| ConcreteState | RestoreViewPhase、ApplyRequestValuesPhase、InvokeApplicationPhase 等 |
核心源码逐步剖析
1. LifecycleImpl.execute()——驱动状态(阶段)链条执行:
public class LifecycleImpl extends Lifecycle {
// 按顺序预设的具体状态数组
private Phase[] phases = new Phase[] {
new RestoreViewPhase(),
new ApplyRequestValuesPhase(),
new ProcessValidationsPhase(),
new UpdateModelValuesPhase(),
new InvokeApplicationPhase(),
new RenderResponsePhase()
};
@Override
public void execute(FacesContext context) throws FacesException {
for (int i = 0, len = phases.length; i < len; i++) {
// 状态机流转控制:检查是否有任何状态标记了跳过后续阶段
if (context.getResponseComplete() || context.getRenderResponse()) {
break;
}
// 执行当前状态的处理逻辑
phases[i].execute(context);
}
}
}
2. ApplyRequestValuesPhase.execute()——具体状态类的内部行为:
public class ApplyRequestValuesPhase extends Phase {
@Override
public void execute(FacesContext context) throws FacesException {
UIViewRoot viewRoot = context.getViewRoot();
viewRoot.processDecodes(context);
// 如果解码过程中发生错误,可通过 context 改变流转信号
// 例如:context.renderResponse(); 让循环直接跳到最后一阶段
}
}
JSF 设计点评:JSF 将复杂的 Web 请求处理过程拆解为 6 个高内聚的具体状态类。每个 Phase 之间是完全解耦的,它们不需要知道上一个或下一个 Phase 是谁,而是通过在 FacesContext 中留下"标记信号"(如 responseComplete)来动态影响 Lifecycle(Context)的执行拓扑链。
2.3 Spring State Machine 源码拆解
在分布式微服务架构下,订单系统不能像 Tomcat 那样在单机内存中维护一个长寿的 Context 对象。Spring State Machine(SSM)最核心的改造是:**状态机配置化(DSL)**以及 Context 无状态化。
角色映射
| Context | StateMachine 接口 |
| State | State<S, E> 接口 |
| Transition | Transition<S, E>(状态转移定义) |
| Action | Action<S, E>(状态转换触发的业务行为) |
| Guard | Guard<S, E>(守卫条件) |
核心源码逐步剖析
1. 通过 Java Config 声明状态和事件的流转拓扑(消灭具体类定义):
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {
@Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) {
states.withStates()
.initial(OrderStates.WAIT_PAY)
.states(EnumSet.allOf(OrderStates.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) {
transitions
.withExternal()
.source(OrderStates.WAIT_PAY).target(OrderStates.PAID)
.event(OrderEvents.PAY_SUCCESS)
.action(paySuccessAction());
}
@Bean
public Action<OrderStates, OrderEvents> paySuccessAction() {
return context -> {
// 这里的 context 是 StateContext,可以注入 Spring 的各种 Service
System.out.println("支付成功,减库存,发通知…");
};
}
}
2. 分布式环境下的无状态驱动与持久化:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private StateMachineService<OrderStates, OrderEvents> stateMachineService;
@Autowired
private OrderRepository orderRepository;
public void handleOrderEvent(Long orderId, OrderEvents event) {
// 从数据库读取订单当前的状态
Order order = orderRepository.findById(orderId).orElseThrow();
// 恢复对应的状态机实例
StateMachine<OrderStates, OrderEvents> stateMachine =
stateMachineService.acquireStateMachine(orderId.toString());
// 将数据库的状态同步给状态机
stateMachine.getStateMachineAccessor()
.doWithAllRegions(accessor -> accessor.resetStateMachine(
new DefaultStateMachineContext<>(order.getState(), null, null, null)
));
// 发送事件,驱动状态机内部的状态模式行为逻辑
stateMachine.sendEvent(event);
// 状态转换成功后,通过监听器将最新状态写回数据库
}
}
3. SSM 核心驱动源码——sendEventInternal():
public class AbstractStateMachine<S, E> {
private synchronized boolean sendEventInternal(Message<E> event) {
// 获取当前状态对象
State<S,E> current = this.state;
// 寻找与当前状态、当前事件相匹配的转移(Transition)
Transition<S,E> transition = findTransition(current, event);
if (transition != null) {
// 触发转移前的守卫检查(Guard)
if (transition.getGuard() != null && !transition.getGuard().evaluate(context)) {
return false;
}
// 执行状态转移绑定的具体 Action 行为
transition.transit(context);
// 变更当前状态指针
this.state = transition.getTarget();
return true;
}
return false;
}
}
三者对比总结:
| Tomcat Lifecycle | 自带校验逻辑的枚举 | LifecycleBase 模板方法强制流转顺序 | 单机组件生命周期各阶段事件的严格顺序广播 |
| JSF Lifecycle | 独立的 Phase 实现类数组 | 外部控制中心(循环驱动)+ 管道内状态信号 | Web 请求处理时各独立阶段的完全解耦 |
| Spring State Machine | 状态/事件枚举 + 配置树 | sendEvent() 触发状态机引擎检索状态转移树 | 分布式业务下复杂多路径状态流转的无状态化持久化管理 |
3. 深度追问
3.1 Why(为什么):解决哪个根本矛盾?
状态模式解决的根本矛盾是:"对象行为随状态变化的无限动态扩展性"与"条件分支硬编码的有限静态结构"之间的矛盾。
在传统设计中,状态被当作对象的属性数据(通常是 int 或 enum),而行为是控制流(if-else)。这种设计将状态的"变化轴"强行塞进了行为的"执行轴"里。当状态变多时,会引发三个层面的崩溃:
- 变动成本非线性增长:增加一个状态,意味着需要去翻遍所有的业务方法,在每一个 switch-case 里加上一个 case NEW_STATE:。
- 状态爆炸与路径迷失:复杂业务往往不是单向流转,而是网状拓扑(如"退款中"可以走向"退款成功"、“退款拒绝”、“撤销退款”)。如果用硬编码,状态转换的逻辑会分散在数百行代码的各个角落。
- 行为与状态的职责倒置:宿主类(Context)本应该关注更高层的业务编排,却被迫去理解"在状态 A 下能不能做操作 B"这种底层的状态守卫控制,导致宿主类极度臃肿。
状态模式的根本出发点,就是将"状态"从"属性数据"升格为"一等公民(对象)",从而让行为的切换依赖于面向对象的多态,而不是依赖于控制流的选择。
3.2 How(怎么做):对象关系与交互机制
状态模式的破局核心在于**组合(Composition)与动态分派(Dynamic Dispatch)**的精妙结合。
核心对象关系:
- Context(环境上下文):状态机的"肉身",维持着长生命周期的业务数据和外部接口。它内部持有一个 State 接口的引用(组合关系)。
- State(抽象状态):状态机的"灵魂/契约",声明了所有外界能够对 Context 发起的动作(Event)。
- ConcreteState(具体状态):状态机的"大脑",内聚了特定状态下的业务逻辑,并且掌握着状态流转的钥匙。
核心交互机制——双向引用与委托流转:
这种"我把你传过去,你反过来修改我"的机制,使得状态的切换对客户端和 Context 的核心结构完全透明。
3.3 Trade-off(代价):牺牲了什么换取什么?
| 类膨胀:哪怕一个状态下只有一个方法有差异,也必须为其建立一个独立的类文件 | 极限的开闭原则:新增状态只需要添加一个类,完全不影响现有状态类的测试与运行 |
| 逻辑碎纸机:完整的状态机拓扑图被撕碎并散落在各个 ConcreteState 类中 | 极致的单一职责:消除巨型类,每个状态类只关心"老子在这个状态下该干嘛" |
| 间接调用开销:引入了额外的对象创建、组合引用以及多态的方法表查找 | 高安全性的状态安全防护:杜绝了因少写 break 或遗漏边界条件导致的状态非法跳跃 |
3.4 Evolution(演化):从诞生到变体
阶段一:初生(GoF 经典的纯面向对象)
- 纯粹靠继承和多态消灭 if-else。状态切换时,不断地在内存中 new ConcreteState()。
- 痛点:状态类频繁创建销毁,耗费内存;状态类之间互相耦合(状态 A 必须知道状态 B 的类才能进行 setState)。
阶段二:改良(享元模式与枚举内聚)
- 引入享元模式:将所有具体状态类设计为无状态的单例。
- 变体(Java Enum 状态机):将状态和行为直接内聚在同一个 Enum 中(如 Tomcat LifecycleState),极大地缓解了类膨胀。
阶段三:现代形态(数据驱动与声明式 DSL)
- 变体(Spring State Machine / Akka FSM):废弃了"由具体状态类控制跳转"的做法。引入了"三元组配置(SourceState, Event, TargetState)"。状态类蜕变为纯粹的 Action(动作执行器),状态的流转图由一处统一的配置(DSL)来定义。
3.5 Modern Practice(现代实践):它被取代了吗?
A. 函数式编程(FP)对它的重塑
在函数式编程中,状态可以直接建模为数据(Data),行为可以直接建模为纯函数(Pure Function)。使用 Map<State, Function<Context, NextState>> 的行为矩阵来彻底取代一整套类继承体系。
B. 依赖注入(Spring IoC)对它的改造
在 Spring 体系下,将状态类定义为 @Component 单例 Bean。Context 只需要持有一个状态的 String 编码,当事件触发时,利用策略工厂从 Spring 容器中动态获取对应的状态 Bean 去执行。
C. 微服务架构下的分布式演进
- Context 彻底脱水(Data-only):状态机 Context 被剥离为纯粹的数据库记录(如订单表中的 status 字段)。
- 状态机引擎作为无状态算力:微服务节点从 Redis/MySQL 中读取当前状态字符串,作为 Key 路由到对应的状态驱动组件,执行完业务 Action 并将新状态写回数据库后,内存立即释放。
- 大厂的终极实践:使用 Temporal、AWS Step Functions、Seata (Saga 模式) 等框架,在分布式集群维度实现了状态的严格状态机流转。
总结:GoF 的经典状态模式作为一种"类设计模式",其代码编写方式在今天确实在逐步退场(被枚举、函数式、声明式配置取代);但它将状态孤立、行为委托、拓扑控制的思想,却原封不动地继承了下来,并成为了现代微服务分布式工作流核心引擎的底层理论基石。
4. 总结
核心要点
- 状态模式将每个状态封装为独立类,消除 if-else,让行为切换依赖于多态
- 核心机制是双向调用闭环:Context 将请求委派给 State,State 处理后通过 Context 切换状态
- 与策略模式的本质区别:状态自动流转,策略由客户端选择
- 三个工业级变体:Tomcat 的枚举式、JSF 的管道式、Spring State Machine 的配置驱动式
一句话
状态模式就是"交通信号灯"——红灯停、绿灯行,状态变了行为也跟着变。它把"状态判断"从硬编码的 if-else 升格为面向对象的多态调用。
5. 参考文献
[1] GAMMA E, HELM R, JOHNSON R, et al. Design Patterns: Elements of Reusable Object-Oriented Software[M]. Boston: Addison-Wesley, 1994. [2] Apache Tomcat. Tomcat Lifecycle Source Code[EB/OL]. https://tomcat.apache.org/, 2024. [3] SPRING. Spring State Machine Documentation[EB/OL]. https://spring.io/projects/spring-statemachine, 2024. [4] Oracle. JavaServer Faces Specification[EB/OL]. https://jcp.org/en/jsr/detail?id=344, 2024.




