1. 状态模式基础:定义、核心角色与适用场景

状态模式,说白了就是让对象在内部状态改变时,能自动切换行为。你想想看,一个对象在不同状态下做同一件事,表现却完全不一样——这种场景在业务代码里太常见了。

我记得刚入行那会儿,接手过一个订单系统。订单状态有新建、已支付、已发货、已完成、已取消……每个状态下的操作逻辑都不一样。当时的前辈用了一堆 if-else 硬写,代码膨胀得厉害。后来我重构时用了状态模式,整个系统清晰多了。

1.1 状态模式的定义

状态模式(State Pattern)属于行为型设计模式。它的核心思想是:将对象的状态封装成独立的类,让对象在状态变化时委托给对应的状态类处理。

用官方的话说:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

一句话总结:状态模式 = 状态驱动行为切换。每个状态只管自己的事,互不干扰。

1.2 核心角色

状态模式有三个核心角色。我习惯把它们比作「遥控器、按钮、具体功能」——你一看就懂。

角色 名称 职责 类比
Context 上下文 持有当前状态引用,对外暴露行为接口 遥控器本体
State 抽象状态 定义状态行为的接口 按钮的抽象功能
ConcreteState 具体状态 实现特定状态下的行为逻辑 按一下开、再按一下关

Context(上下文)

Context 是客户端直接交互的对象。它维护一个 State 引用,所有请求都委托给当前 State 处理。Context 本身不关心状态切换的具体逻辑——它只负责「换人」。

State(抽象状态)

State 是一个接口或抽象类,定义了一组行为方法。这些方法对应 Context 在不同状态下需要执行的操作。比如订单系统里,State 会定义 pay()、ship()、cancel() 等方法。

ConcreteState(具体状态)

每个 ConcreteState 实现 State 接口,只关注自己状态下的行为。比如「已支付」状态下的 cancel() 可以退款,但「已发货」状态下的 cancel() 可能就不允许了。

我的习惯:写状态模式时,我会先画一张状态转换图,把每个状态允许的操作和下一个状态标清楚。这样 ConcreteState 里的逻辑就不会乱。

1.3 核心逻辑图解

下面这张图展示了状态模式的核心结构。我特意把 Context、State、ConcreteState 的关系画清楚了——箭头表示委托调用,虚线表示状态切换。

状态模式核心结构图 状态模式核心结构 Context 持有 State 引用 State(接口) 定义行为方法 委托调用 ConcreteStateA 实现状态A的行为 ConcreteStateB 实现状态B的行为 ConcreteStateC 实现状态C的行为 切换 切换 说明: Context 将行为委托给当前 State 对象;State 接口定义行为契约;ConcreteState 实现具体逻辑。 状态切换由 ConcreteState 内部触发,或由 Context 统一管理。

1.4 适用场景分析

状态模式不是万能的。我见过不少团队滥用它,把简单的逻辑搞复杂了。到底什么时候该用?我总结了四个典型场景。

场景一:对象行为随状态变化

这是最直接的场景。一个对象在不同状态下,对同一请求做出不同响应。比如 TCP 连接的状态机:CLOSED 状态下调用 open() 会建立连接,ESTABLISHED 状态下调用 open() 可能直接报错。

场景二:大量条件分支

代码里充斥着 if (state == A) {...} else if (state == B) {...} 这种结构。每次新增状态都要改一大片代码,容易改出 bug。

我曾经踩过的坑:有个项目用 switch-case 处理 8 种状态,每个 case 里还有嵌套条件。后来加第 9 种状态时,改漏了一个分支,线上出了 P0 事故。重构为状态模式后,每种状态独立成类,新增状态只需加一个类,再也不用担心改漏了。

场景三:状态转换规则复杂

状态之间不是随意切换的,有严格的转换图。比如工作流引擎:草稿→审批中→已通过/已驳回。状态模式把转换逻辑封装在每个 ConcreteState 里,清晰可控。

场景四:多个对象共享状态逻辑

当多个 Context 对象需要复用同一套状态行为时,状态模式天然支持。比如游戏里的 NPC,多个 NPC 可以共享「巡逻」「追击」「攻击」等状态实例。

1.5 什么时候别用状态模式

嗯,这里要注意。状态模式也有不适合的场景:

  • 状态很少且稳定:比如只有 2-3 个状态,且几乎不变。用 if-else 反而更直接。
  • 状态行为差异极小:如果不同状态下的行为只有一两行代码不同,没必要拆成独立类。
  • 性能敏感且状态切换极频繁:每个请求都创建/销毁状态对象会有开销。当然,可以用享元模式优化。

我的建议:先写 if-else,等状态超过 4 个或者条件分支开始嵌套时,再考虑重构为状态模式。过早优化是万恶之源。

1.6 一个简单的代码示例

光说不练假把式。我写一个最简单的状态模式示例——电灯开关。只有两个状态:开和关。

// 抽象状态
interface State {
    void handle(Context context);
}

// 具体状态:开
class OnState implements State {
    @Override
    public void handle(Context context) {
        System.out.println("灯已亮,切换到关");
        context.setState(new OffState());
    }
}

// 具体状态:关
class OffState implements State {
    @Override
    public void handle(Context context) {
        System.out.println("灯已灭,切换到开");
        context.setState(new OnState());
    }
}

// 上下文
class Context {
    private State state;

    public Context() {
        this.state = new OffState(); // 初始状态
    }

    public void setState(State state) {
        this.state = state;
    }

    public void request() {
        state.handle(this);
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.request(); // 灯已灭,切换到开
        context.request(); // 灯已亮,切换到关
        context.request(); // 灯已灭,切换到开
    }
}

你看,每个状态只关心自己的行为。新增一个「闪烁」状态,只需要加一个 BlinkState 类,不用改任何现有代码——这就是状态模式最大的好处。

1.7 本章小结

状态模式的核心就三件事:封装状态、委托行为、解耦切换。它把复杂的状态逻辑拆散到独立类里,让代码可读性和可维护性都上了一个台阶。

我个人习惯在项目初期先画状态转换图,把每个状态的行为和转换条件列清楚。这样写出来的状态模式代码,后期几乎不用改——除非产品经理又加新状态了。嗯,你懂的。