状态模式

让对象在状态改变时自动切换行为

问题

什么是状态模式?如何在 JavaScript 中实现状态模式?

解答

状态模式允许对象在内部状态改变时改变其行为,看起来像是改变了对象的类。它将状态相关的行为封装到独立的状态类中,避免大量的条件判断语句。

基本实现

// 状态接口(抽象状态)
class State {
  handle(context) {
    throw new Error('子类必须实现 handle 方法')
  }
}

// 具体状态:待机状态
class OffState extends State {
  handle(context) {
    console.log('当前是关机状态,正在开机...')
    context.setState(new OnState())
  }
}

// 具体状态:开机状态
class OnState extends State {
  handle(context) {
    console.log('当前是开机状态,正在关机...')
    context.setState(new OffState())
  }
}

// 上下文:持有状态并委托行为
class Switch {
  constructor() {
    // 初始状态为关机
    this.state = new OffState()
  }

  setState(state) {
    this.state = state
  }

  // 按下开关,委托给当前状态处理
  press() {
    this.state.handle(this)
  }
}

// 使用
const switcher = new Switch()
switcher.press() // 当前是关机状态,正在开机...
switcher.press() // 当前是开机状态,正在关机...
switcher.press() // 当前是关机状态,正在开机...

实际场景:订单状态流转

// 订单状态基类
class OrderState {
  constructor(order) {
    this.order = order
  }

  pay() {
    console.log('当前状态不允许支付')
  }

  ship() {
    console.log('当前状态不允许发货')
  }

  receive() {
    console.log('当前状态不允许确认收货')
  }

  cancel() {
    console.log('当前状态不允许取消')
  }
}

// 待支付状态
class PendingPayment extends OrderState {
  pay() {
    console.log('支付成功')
    this.order.setState(new PendingShipment(this.order))
  }

  cancel() {
    console.log('订单已取消')
    this.order.setState(new Cancelled(this.order))
  }
}

// 待发货状态
class PendingShipment extends OrderState {
  ship() {
    console.log('已发货')
    this.order.setState(new Shipped(this.order))
  }
}

// 已发货状态
class Shipped extends OrderState {
  receive() {
    console.log('确认收货,订单完成')
    this.order.setState(new Completed(this.order))
  }
}

// 已完成状态
class Completed extends OrderState {
  // 所有操作都不允许,使用默认行为
}

// 已取消状态
class Cancelled extends OrderState {
  // 所有操作都不允许,使用默认行为
}

// 订单上下文
class Order {
  constructor() {
    this.state = new PendingPayment(this)
  }

  setState(state) {
    this.state = state
    console.log(`订单状态变更为: ${state.constructor.name}`)
  }

  pay() {
    this.state.pay()
  }

  ship() {
    this.state.ship()
  }

  receive() {
    this.state.receive()
  }

  cancel() {
    this.state.cancel()
  }
}

// 使用
const order = new Order()
order.ship()    // 当前状态不允许发货
order.pay()     // 支付成功 -> 订单状态变更为: PendingShipment
order.cancel()  // 当前状态不允许取消
order.ship()    // 已发货 -> 订单状态变更为: Shipped
order.receive() // 确认收货,订单完成 -> 订单状态变更为: Completed

简化版:使用对象字面量

// 用对象代替类,更轻量
const trafficLight = {
  // 状态定义
  states: {
    green: {
      color: '绿灯',
      next: 'yellow',
      duration: 30
    },
    yellow: {
      color: '黄灯',
      next: 'red',
      duration: 5
    },
    red: {
      color: '红灯',
      next: 'green',
      duration: 25
    }
  },

  // 当前状态
  current: 'green',

  // 获取当前状态信息
  getInfo() {
    const state = this.states[this.current]
    return `${state.color},持续 ${state.duration} 秒`
  },

  // 切换到下一个状态
  change() {
    const state = this.states[this.current]
    this.current = state.next
    console.log(`切换到: ${this.getInfo()}`)
  }
}

// 使用
console.log(trafficLight.getInfo()) // 绿灯,持续 30 秒
trafficLight.change() // 切换到: 黄灯,持续 5 秒
trafficLight.change() // 切换到: 红灯,持续 25 秒
trafficLight.change() // 切换到: 绿灯,持续 30 秒

关键点

  • 消除条件分支:用多态代替 if-else 或 switch-case,每个状态类只关心自己的行为
  • 状态转换封装:状态切换逻辑在状态类内部完成,上下文不需要知道转换规则
  • 开闭原则:新增状态只需添加新的状态类,不修改现有代码
  • 适用场景:对象行为随状态改变而改变,且状态数量较多时(如订单流程、游戏角色状态、UI 组件状态)
  • 与策略模式区别:状态模式的状态之间知道彼此存在并主动切换,策略模式的策略相互独立由外部选择