Skip to content

状态模式(State Pattern)

概念

状态模式是一种行为设计模式,它允许对象在内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式将对象的行为封装在不同的状态对象中,通过切换状态对象来改变对象的行为。

状态模式的核心思想是将对象的状态抽象为独立的类,每个状态类都实现相同的接口,对象通过委托给当前状态对象来处理请求。当状态改变时,对象会切换到另一个状态对象,从而改变其行为。

基本实现

状态模式包含以下主要角色:

  1. State(抽象状态类):定义一个接口以封装与Context的一个特定状态相关的行为。
  2. ConcreteState(具体状态类):每一个子类实现一个与Context的一个状态相关的行为。
  3. Context(环境类):维护一个ConcreteState子类的实例,这个实例定义当前的状态。

下面是状态模式的基本实现:

javascript
// 抽象状态类
class State {
  handle(context) {
    throw new Error('handle method must be implemented');
  }
}

// 具体状态A
class ConcreteStateA extends State {
  handle(context) {
    console.log('当前状态是 A,执行A状态的行为');
    // 切换到状态B
    context.setState(new ConcreteStateB());
  }
}

// 具体状态B
class ConcreteStateB extends State {
  handle(context) {
    console.log('当前状态是 B,执行B状态的行为');
    // 切换到状态A
    context.setState(new ConcreteStateA());
  }
}

// 环境类
class Context {
  constructor() {
    this.state = new ConcreteStateA(); // 初始状态
  }

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

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

// 使用示例
const context = new Context();

context.request(); // 状态A
context.request(); // 状态B
context.request(); // 状态A
context.request(); // 状态B

实际应用场景

场景1:订单状态管理系统

在电商系统中,订单有不同的状态(待付款、已付款、已发货、已完成、已取消等),每个状态有不同的行为和可执行的操作。

javascript
// 订单状态接口
class OrderState {
  pay(order) {
    throw new Error('pay method not implemented');
  }

  ship(order) {
    throw new Error('ship method not implemented');
  }

  deliver(order) {
    throw new Error('deliver method not implemented');
  }

  cancel(order) {
    throw new Error('cancel method not implemented');
  }

  getStateName() {
    throw new Error('getStateName method not implemented');
  }
}

// 待付款状态
class PendingPaymentState extends OrderState {
  pay(order) {
    console.log('订单已付款');
    order.setState(new PaidState());
  }

  ship(order) {
    console.log('订单还未付款,无法发货');
  }

  deliver(order) {
    console.log('订单还未付款,无法配送');
  }

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

  getStateName() {
    return '待付款';
  }
}

// 已付款状态
class PaidState extends OrderState {
  pay(order) {
    console.log('订单已付款,无需重复付款');
  }

  ship(order) {
    console.log('订单已发货');
    order.setState(new ShippedState());
  }

  deliver(order) {
    console.log('订单还未发货,无法配送');
  }

  cancel(order) {
    console.log('订单已取消,将退款');
    order.setState(new CancelledState());
  }

  getStateName() {
    return '已付款';
  }
}

// 已发货状态
class ShippedState extends OrderState {
  pay(order) {
    console.log('订单已发货,无法付款');
  }

  ship(order) {
    console.log('订单已发货,无需重复发货');
  }

  deliver(order) {
    console.log('订单已配送');
    order.setState(new DeliveredState());
  }

  cancel(order) {
    console.log('订单已在配送途中,无法取消');
  }

  getStateName() {
    return '已发货';
  }
}

// 已配送状态
class DeliveredState extends OrderState {
  pay(order) {
    console.log('订单已完成,无法付款');
  }

  ship(order) {
    console.log('订单已完成,无法发货');
  }

  deliver(order) {
    console.log('订单已完成,无需重复配送');
  }

  cancel(order) {
    console.log('订单已完成,无法取消');
  }

  getStateName() {
    return '已配送';
  }
}

// 已取消状态
class CancelledState extends OrderState {
  pay(order) {
    console.log('订单已取消,无法付款');
  }

  ship(order) {
    console.log('订单已取消,无法发货');
  }

  deliver(order) {
    console.log('订单已取消,无法配送');
  }

  cancel(order) {
    console.log('订单已取消,无需重复取消');
  }

  getStateName() {
    return '已取消';
  }
}

// 订单类(环境类)
class Order {
  constructor(orderId) {
    this.orderId = orderId;
    this.state = new PendingPaymentState(); // 初始状态为待付款
  }

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

  getStateName() {
    return this.state.getStateName();
  }

  pay() {
    console.log(`订单 ${this.orderId} 尝试付款...`);
    this.state.pay(this);
  }

  ship() {
    console.log(`订单 ${this.orderId} 尝试发货...`);
    this.state.ship(this);
  }

  deliver() {
    console.log(`订单 ${this.orderId} 尝试配送...`);
    this.state.deliver(this);
  }

  cancel() {
    console.log(`订单 ${this.orderId} 尝试取消...`);
    this.state.cancel(this);
  }

  displayStatus() {
    console.log(`订单 ${this.orderId} 当前状态: ${this.getStateName()}\n`);
  }
}

// 使用示例
const order = new Order('ORD-001');

order.displayStatus();

// 尝试各种操作
order.ship();    // 无法发货
order.deliver(); // 无法配送
order.pay();     // 付款成功
order.displayStatus();

order.ship();    // 发货成功
order.displayStatus();

order.deliver(); // 配送成功
order.displayStatus();

// 尝试在已完成状态下操作
order.pay();     // 无法付款
order.ship();    // 无法发货
order.cancel();  // 无法取消

场景2:媒体播放器状态控制

在媒体播放器中,播放状态(播放、暂停、停止)决定了可以执行的操作,使用状态模式可以很好地管理这些状态转换。

javascript
// 播放器状态接口
class PlayerState {
  play(player) {
    throw new Error('play method not implemented');
  }

  pause(player) {
    throw new Error('pause method not implemented');
  }

  stop(player) {
    throw new Error('stop method not implemented');
  }

  getStateName() {
    throw new Error('getStateName method not implemented');
  }
}

// 停止状态
class StoppedState extends PlayerState {
  play(player) {
    console.log('开始播放');
    player.setState(new PlayingState());
  }

  pause(player) {
    console.log('播放器已停止,无法暂停');
  }

  stop(player) {
    console.log('播放器已停止');
  }

  getStateName() {
    return '停止';
  }
}

// 播放状态
class PlayingState extends PlayerState {
  play(player) {
    console.log('正在播放中');
  }

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

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

  getStateName() {
    return '播放';
  }
}

// 暂停状态
class PausedState extends PlayerState {
  play(player) {
    console.log('继续播放');
    player.setState(new PlayingState());
  }

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

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

  getStateName() {
    return '暂停';
  }
}

// 媒体播放器类
class MediaPlayer {
  constructor() {
    this.state = new StoppedState(); // 初始状态为停止
    this.currentMedia = null;
  }

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

  getStateName() {
    return this.state.getStateName();
  }

  loadMedia(media) {
    this.currentMedia = media;
    console.log(`加载媒体: ${media}`);
  }

  play() {
    console.log('播放器尝试播放...');
    this.state.play(this);
  }

  pause() {
    console.log('播放器尝试暂停...');
    this.state.pause(this);
  }

  stop() {
    console.log('播放器尝试停止...');
    this.state.stop(this);
  }

  displayStatus() {
    console.log(`播放器状态: ${this.getStateName()}`);
    if (this.currentMedia) {
      console.log(`当前媒体: ${this.currentMedia}`);
    }
    console.log('');
  }
}

// 使用示例
const player = new MediaPlayer();

player.loadMedia('song.mp3');
player.displayStatus();

// 尝试各种操作
player.pause(); // 无法暂停
player.stop();  // 已停止
player.play();  // 开始播放
player.displayStatus();

player.pause(); // 暂停播放
player.displayStatus();

player.play();  // 继续播放
player.displayStatus();

player.stop();  // 停止播放
player.displayStatus();

// 在停止状态下尝试操作
player.pause(); // 无法暂停

场景3:游戏角色状态系统

在游戏开发中,角色可能有多种状态(正常、中毒、眩晕、隐身等),每种状态会影响角色的行为和能力。

javascript
// 角色状态接口
class CharacterState {
  move(character) {
    throw new Error('move method not implemented');
  }

  attack(character, target) {
    throw new Error('attack method not implemented');
  }

  takeDamage(character, damage) {
    throw new Error('takeDamage method not implemented');
  }

  useSkill(character, skill) {
    throw new Error('useSkill method not implemented');
  }

  getStateName() {
    throw new Error('getStateName method not implemented');
  }

  // 状态更新(每帧调用)
  update(character) {
    // 默认实现,子类可重写
  }
}

// 正常状态
class NormalState extends CharacterState {
  move(character) {
    console.log(`${character.name} 正常移动`);
  }

  attack(character, target) {
    const damage = character.attackPower;
    console.log(`${character.name} 攻击 ${target.name} 造成 ${damage} 点伤害`);
    target.takeDamage(damage);
  }

  takeDamage(character, damage) {
    character.health -= damage;
    console.log(`${character.name} 受到 ${damage} 点伤害,剩余生命值: ${character.health}`);
    
    if (character.health <= 0) {
      console.log(`${character.name} 被击败!`);
    }
  }

  useSkill(character, skill) {
    console.log(`${character.name} 使用技能: ${skill}`);
    return true;
  }

  getStateName() {
    return '正常';
  }
}

// 中毒状态
class PoisonedState extends CharacterState {
  constructor() {
    super();
    this.duration = 5; // 持续5个回合
    this.poisonDamage = 10;
  }

  move(character) {
    console.log(`${character.name} 中毒状态下缓慢移动`);
  }

  attack(character, target) {
    const damage = Math.floor(character.attackPower * 0.8); // 攻击力降低
    console.log(`${character.name} 中毒状态下攻击 ${target.name} 造成 ${damage} 点伤害`);
    target.takeDamage(damage);
  }

  takeDamage(character, damage) {
    const actualDamage = damage * 1.2; // 受到更多伤害
    character.health -= actualDamage;
    console.log(`${character.name} 中毒状态下受到 ${actualDamage} 点伤害(+20%),剩余生命值: ${character.health}`);
    
    if (character.health <= 0) {
      console.log(`${character.name} 被击败!`);
    }
  }

  useSkill(character, skill) {
    console.log(`${character.name} 中毒状态下无法使用技能 ${skill}`);
    return false;
  }

  update(character) {
    this.duration--;
    character.health -= this.poisonDamage;
    console.log(`${character.name} 受到 ${this.poisonDamage} 点毒性伤害,剩余生命值: ${character.health},中毒状态剩余 ${this.duration} 回合`);
    
    if (this.duration <= 0) {
      console.log(`${character.name} 中毒状态结束`);
      character.setState(new NormalState());
    }
  }

  getStateName() {
    return `中毒 (${this.duration}回合)`;
  }
}

// 眩晕状态
class StunnedState extends CharacterState {
  constructor() {
    super();
    this.duration = 2; // 持续2个回合
  }

  move(character) {
    console.log(`${character.name} 眩晕状态下无法移动`);
  }

  attack(character, target) {
    console.log(`${character.name} 眩晕状态下无法攻击`);
  }

  takeDamage(character, damage) {
    character.health -= damage;
    console.log(`${character.name} 眩晕状态下受到 ${damage} 点伤害,剩余生命值: ${character.health}`);
    
    if (character.health <= 0) {
      console.log(`${character.name} 被击败!`);
    }
  }

  useSkill(character, skill) {
    console.log(`${character.name} 眩晕状态下无法使用技能 ${skill}`);
    return false;
  }

  update(character) {
    this.duration--;
    console.log(`${character.name} 眩晕状态剩余 ${this.duration} 回合`);
    
    if (this.duration <= 0) {
      console.log(`${character.name} 眩晕状态结束`);
      character.setState(new NormalState());
    }
  }

  getStateName() {
    return `眩晕 (${this.duration}回合)`;
  }
}

// 隐身状态
class InvisibleState extends CharacterState {
  constructor() {
    super();
    this.duration = 3; // 持续3个回合
  }

  move(character) {
    console.log(`${character.name} 隐身状态下快速移动`);
  }

  attack(character, target) {
    const damage = Math.floor(character.attackPower * 1.5); // 隐身攻击有加成
    console.log(`${character.name} 从隐身状态发动突袭,对 ${target.name} 造成 ${damage} 点暴击伤害`);
    target.takeDamage(damage);
    // 攻击后解除隐身
    character.setState(new NormalState());
  }

  takeDamage(character, damage) {
    const actualDamage = Math.floor(damage * 0.5); // 受到伤害减半
    character.health -= actualDamage;
    console.log(`${character.name} 隐身状态下受到 ${actualDamage} 点伤害(-50%),剩余生命值: ${character.health}`);
    
    if (character.health <= 0) {
      console.log(`${character.name} 被击败!`);
    }
  }

  useSkill(character, skill) {
    console.log(`${character.name} 隐身状态下使用技能: ${skill}`);
    // 使用技能后有一定概率解除隐身
    if (Math.random() < 0.3) {
      console.log(`${character.name} 使用技能后暴露了位置`);
      character.setState(new NormalState());
    }
    return true;
  }

  update(character) {
    this.duration--;
    console.log(`${character.name} 隐身状态剩余 ${this.duration} 回合`);
    
    if (this.duration <= 0) {
      console.log(`${character.name} 隐身状态结束`);
      character.setState(new NormalState());
    }
  }

  getStateName() {
    return `隐身 (${this.duration}回合)`;
  }
}

// 游戏角色类
class GameCharacter {
  constructor(name, health, attackPower) {
    this.name = name;
    this.health = health;
    this.maxHealth = health;
    this.attackPower = attackPower;
    this.state = new NormalState();
  }

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

  getStateName() {
    return this.state.getStateName();
  }

  move() {
    console.log(`${this.name} 尝试移动...`);
    this.state.move(this);
  }

  attack(target) {
    console.log(`${this.name} 尝试攻击...`);
    this.state.attack(this, target);
  }

  takeDamage(damage) {
    this.state.takeDamage(this, damage);
  }

  useSkill(skill) {
    console.log(`${this.name} 尝试使用技能...`);
    return this.state.useSkill(this, skill);
  }

  // 被施加中毒状态
  applyPoison() {
    if (!(this.state instanceof PoisonedState)) {
      console.log(`${this.name} 被施加了中毒状态!`);
      this.setState(new PoisonedState());
    } else {
      console.log(`${this.name} 已经中毒了`);
    }
  }

  // 被施加眩晕状态
  applyStun() {
    if (!(this.state instanceof StunnedState)) {
      console.log(`${this.name} 被眩晕了!`);
      this.setState(new StunnedState());
    } else {
      console.log(`${this.name} 已经被眩晕了`);
    }
  }

  // 进入隐身状态
  turnInvisible() {
    if (!(this.state instanceof InvisibleState)) {
      console.log(`${this.name} 进入了隐身状态!`);
      this.setState(new InvisibleState());
    } else {
      console.log(`${this.name} 已经隐身了`);
    }
  }

  // 状态更新(每回合调用)
  update() {
    this.state.update(this);
  }

  displayStatus() {
    console.log(`\n=== ${this.name} 状态 ===`);
    console.log(`生命值: ${this.health}/${this.maxHealth}`);
    console.log(`攻击力: ${this.attackPower}`);
    console.log(`当前状态: ${this.getStateName()}\n`);
  }
}

// 使用示例
const player = new GameCharacter('勇士', 100, 20);
const enemy = new GameCharacter('怪物', 80, 15);

player.displayStatus();
enemy.displayStatus();

console.log('=== 战斗开始 ===');

// 正常状态下的行动
player.move();
player.attack(enemy);
enemy.displayStatus();

// 敌人中毒
enemy.applyPoison();
enemy.displayStatus();

// 中毒状态下的行动
enemy.move();
enemy.attack(player);
player.displayStatus();

// 玩家眩晕
player.applyStun();
player.displayStatus();

// 眩晕状态下的行动
player.move();
player.attack(enemy);

// 更新状态(模拟回合进行)
console.log('\n=== 回合更新 ===');
for (let i = 0; i < 3; i++) {
  console.log(`\n--- 第 ${i + 1} 回合 ---`);
  player.update();
  enemy.update();
  player.displayStatus();
  enemy.displayStatus();
}

// 隐身状态
player.turnInvisible();
player.displayStatus();

player.move();
player.useSkill('隐身术');
player.attack(enemy); // 隐身攻击
enemy.displayStatus();
player.displayStatus();

关键要点

  1. 状态封装:将每个状态的行为封装在独立的类中,符合单一职责原则。

  2. 状态转换:状态之间的转换由具体状态类控制,而不是由环境类控制。

  3. 消除大量条件语句:避免使用大量的if-else或switch语句来处理不同状态的行为。

  4. 易于扩展:增加新的状态只需要创建新的状态类,无需修改现有代码。

与其他模式的关系

  • 与策略模式区别:策略模式是让客户端选择算法,而状态模式是状态对象自己决定状态转换。
  • 与有限状态机结合:状态模式是面向对象的有限状态机实现方式。
  • 与单例模式结合:如果状态对象没有实例变量,可以使用单例模式共享状态实例。

优缺点

优点:

  1. 消除复杂的条件分支语句:将各种状态的逻辑分布到不同的状态类中。
  2. 易于增加新的状态:增加新状态只需要创建新的状态类,符合开闭原则。
  3. 状态转换明确:状态转换逻辑封装在状态类中,转换更加清晰。
  4. 更好的可维护性:每个状态的逻辑独立,便于维护和测试。

缺点:

  1. 增加类的数量:每增加一个状态就需要增加一个类,可能导致类膨胀。
  2. 状态逻辑分散:状态逻辑分布在多个类中,可能增加理解难度。
  3. 对象开销:频繁的状态切换可能产生较多的对象创建和销毁开销。

状态模式在处理复杂的对象状态转换和行为变化时非常有用,特别是在游戏开发、工作流引擎、协议处理等领域有广泛应用。通过合理使用状态模式,可以使代码更加清晰、可维护和可扩展。