State Pattern

用状态模式管理对象的状态切换和行为变化

问题

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

解答

状态模式让对象在内部状态改变时改变其行为,看起来像是改变了对象的类。

基本实现

以音乐播放器为例:

// 状态接口
class State {
  play() {}
  pause() {}
  stop() {}
}

// 具体状态:停止状态
class StoppedState extends State {
  constructor(player) {
    super();
    this.player = player;
  }

  play() {
    console.log('开始播放');
    this.player.setState(this.player.playingState);
  }

  pause() {
    console.log('已经停止,无法暂停');
  }

  stop() {
    console.log('已经是停止状态');
  }
}

// 具体状态:播放状态
class PlayingState extends State {
  constructor(player) {
    super();
    this.player = player;
  }

  play() {
    console.log('已经在播放');
  }

  pause() {
    console.log('暂停播放');
    this.player.setState(this.player.pausedState);
  }

  stop() {
    console.log('停止播放');
    this.player.setState(this.player.stoppedState);
  }
}

// 具体状态:暂停状态
class PausedState extends State {
  constructor(player) {
    super();
    this.player = player;
  }

  play() {
    console.log('继续播放');
    this.player.setState(this.player.playingState);
  }

  pause() {
    console.log('已经暂停');
  }

  stop() {
    console.log('停止播放');
    this.player.setState(this.player.stoppedState);
  }
}

// 上下文:播放器
class MusicPlayer {
  constructor() {
    // 初始化所有状态
    this.stoppedState = new StoppedState(this);
    this.playingState = new PlayingState(this);
    this.pausedState = new PausedState(this);

    // 初始状态
    this.state = this.stoppedState;
  }

  setState(state) {
    this.state = state;
  }

  // 委托给当前状态处理
  play() {
    this.state.play();
  }

  pause() {
    this.state.pause();
  }

  stop() {
    this.state.stop();
  }
}

// 使用
const player = new MusicPlayer();
player.play();   // 开始播放
player.pause();  // 暂停播放
player.play();   // 继续播放
player.stop();   // 停止播放
player.pause();  // 已经停止,无法暂停

简化版:用对象字面量

// 状态机配置
const stateMachine = {
  stopped: {
    play: 'playing',
    pause: null,
    stop: null
  },
  playing: {
    play: null,
    pause: 'coulg',
    stop: 'stopped'
  },
  paused: {
    play: 'playing',
    pause: null,
    stop: 'stopped'
  }
};

class SimplePlayer {
  constructor() {
    this.state = 'stopped';
  }

  transition(action) {
    const nextState = stateMachine[this.state][action];
    if (nextState) {
      console.log(`${this.state} -> ${nextState}`);
      this.state = nextState;
    } else {
      console.log(`无法在 ${this.state} 状态执行 ${action}`);
    }
  }

  play() { this.transition('play'); }
  pause() { this.transition('pause'); }
  stop() { this.transition('stop'); }
}

const player = new SimplePlayer();
player.play();   // stopped -> playing
player.pause();  // playing -> paused
player.play();   // coulg -> playing

实际应用:订单状态

class Order {
  constructor() {
    this.states = {
      pending: {
        pay: () => {
          console.log('支付成功');
          this.state = 'paid';
        },
        cancel: () => {
          console.log('订单取消');
          this.state = 'cancelled';
        }
      },
      paid: {
        ship: () => {
          console.log('已发货');
          this.state = 'shipped';
        },
        refund: () => {
          console.log('退款成功');
          this.state = 'refunded';
        }
      },
      shipped: {
        receive: () => {
          console.log('确认收货');
          this.state = 'completed';
        }
      },
      completed: {},
      cancelled: {},
      refunded: {}
    };
    this.state = 'pending';
  }

  dispatch(action) {
    const handler = this.states[this.state][action];
    if (handler) {
      handler();
    } else {
      console.log(`当前状态 ${this.state} 不支持 ${action} 操作`);
    }
  }
}

const order = new Order();
order.dispatch('pay');     // 支付成功
order.dispatch('ship');    // 已发货
order.dispatch('receive'); // 确认收货
order.dispatch('refund');  // 当前状态 completed 不支持 refund 操作

关键点

  • 将状态相关的行为封装到独立的状态类中,消除大量条件判断
  • 状态切换由状态对象自己控制,符合开闭原则
  • 上下文对象持有当前状态,并将请求委托给状态对象处理
  • 适用于对象行为随状态改变而改变的场景(订单、流程、游戏等)
  • 状态较少时可用对象字面量简化,复杂场景用类实现