Skip to content

状态模式

1. 核心概念与价值

状态模式的定义是:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

它的核心思想是:将每一种状态封装成一个独立的类/对象,并将请求委托给当前状态对象。

维度描述
核心目的消除庞大的条件分支语句,将状态逻辑分布到各个状态类中。
主要优点1. 结构清晰,避免了状态判断的逻辑堆积;
2. 封装性好,将“状态切换”和“状态行为”结合在一起;
3. 扩展性强,新增状态只需增加一个新类,符合开闭原则。
主要缺点1. 增加系统类/对象的数量;
2. 逻辑分散,如果状态极多且跳转复杂,可能导致“迷路”。

2. 模式演进:从“面条代码”到“状态机”

让我们以一个简单的 “交通灯” 系统为例。

1. 传统写法(难以维护)

这种写法在增加新状态(如“黄灯闪烁”)时,需要改动整个函数逻辑。

js
class TrafficLight {
  constructor() {
    this.state = 'red';
  }
  
  change() {
    if (this.state === 'red') {
      console.log('红灯停 -> 变绿灯');
      this.state = 'green';
    } else if (this.state === 'green') {
      console.log('绿灯行 -> 变黄灯');
      this.state = 'yellow';
    } else if (this.state === 'yellow') {
      console.log('黄灯等 -> 变红灯');
      this.state = 'red';
    }
  }
}

2. 状态模式写法(解耦版)

我们将行为委托给具体的状态对象。

js
// 1. 定义状态对象集合
const States = {
  red: {
    handle(context) {
      console.log('🔴 红灯停 -> 下一站:绿灯');
      context.setState(States.green); // 状态自动流转
    }
  },
  green: {
    handle(context) {
      console.log('🟢 绿灯行 -> 下一站:黄灯');
      context.setState(States.yellow);
    }
  },
  yellow: {
    handle(context) {
      console.log('🟡 黄灯等 -> 下一站:红灯');
      context.setState(States.red);
    }
  }
};

// 2. 环境类 (Context)
class TrafficLight {
  constructor() {
    this.currentState = States.red; // 初始状态
  }
  setState(newState) {
    this.currentState = newState;
  }
  request() {
    this.currentState.handle(this);
  }
}

// 使用
const light = new TrafficLight();
light.request(); // 🔴 红灯停...
light.request(); // 🟢 绿灯行...

3. 实战场景:工作流与复杂 UI

状态模式在以下场景中几乎是“标准答案”:

  1. 文件审批流草稿 -> 待审批 -> 已发布 -> 已下线
  2. 网络游戏状态加载中 -> 菜单页 -> 战斗中 -> 结算中
  3. 复杂组件 UI:例如一个上传组件,有 等待上传上传中暂停成功失败 状态,不同状态下按钮的文字和功能完全不同。

4. 常见问题 (FAQ)

4.1 状态模式和策略模式(Strategy Pattern)有什么区别?

这是最经典的问题。它们结构非常相似,但意图完全不同:

  • 策略模式:不同的算法之间是平等、独立的。由外部调用者决定用哪一个。它们通常不互相知道对方的存在。
  • 状态模式:状态之间是有联系、有流转的。状态的切换通常是由状态内部逻辑自动触发的。目的是表现对象在不同生命周期的不同行为。

4.2 状态模式会增加很多对象,值得吗?

  • :如果你的状态只有 2 个(如 开/关),用布尔值即可。但如果状态超过 3 个,且每个状态下的操作逻辑很重,那么增加对象的成本远低于维护“面条代码”的成本。

4.3 如何处理状态转换非常复杂的情况?

  • :如果状态转换变成了一个巨大的迷宫,手动写状态模式可能会出错。此时推荐使用 有限状态机 (FSM) 库。
    • 在 JavaScript 社区,XState 是目前的行业标准,它基于状态图理论,能处理极复杂的逻辑。

4.4 状态对象需要存储在 Context 内部还是外部?

    • 如果状态是无状态的(即所有实例共用逻辑),可以像我上文示例中那样放在外部常量中。
    • 如果每个状态需要存储不同的私有数据,则应该在 Context 实例化时为每个状态创建独立实例。