Skip to content

命令模式(Command Pattern)

概念

命令模式是一种行为设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式将发出请求的对象(调用者)与执行请求的对象(接收者)解耦,通过引入一个命令对象来封装请求的所有信息,包括接收者、操作和参数。

基本实现

在命令模式中,通常包含以下几个核心角色:

  1. Command(命令接口):声明执行操作的接口。
  2. ConcreteCommand(具体命令):将一个接收者对象和一个或多个操作绑定在一起。
  3. Invoker(调用者):要求命令执行请求。
  4. Receiver(接收者):知道如何实施与执行一个请求相关的操作。

下面是一个简单的命令模式实现:

javascript
// 接收者 - 执行实际操作的对象
class Light {
  turnOn() {
    console.log('Light is ON');
  }

  turnOff() {
    console.log('Light is OFF');
  }
}

// 命令接口
class Command {
  execute() {
    throw new Error('execute method must be implemented');
  }
  
  undo() {
    throw new Error('undo method must be implemented');
  }
}

// 具体命令 - 打开灯
class TurnOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOn();
  }
  
  undo() {
    this.light.turnOff();
  }
}

// 具体命令 - 关闭灯
class TurnOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOff();
  }
  
  undo() {
    this.light.turnOn();
  }
}

// 调用者 - 遥控器
class RemoteControl {
  constructor() {
    this.commands = [];
    this.undoCommands = [];
  }

  setCommand(slot, command) {
    this.commands[slot] = command;
  }

  pressButton(slot) {
    if (this.commands[slot]) {
      this.commands[slot].execute();
      this.undoCommands.push(this.commands[slot]);
    }
  }
  
  pressUndo() {
    const lastCommand = this.undoCommands.pop();
    if (lastCommand) {
      lastCommand.undo();
    }
  }
}

// 使用示例
const light = new Light();
const turnOnCommand = new TurnOnCommand(light);
const turnOffCommand = new TurnOffCommand(light);

const remote = new RemoteControl();
remote.setCommand(0, turnOnCommand);
remote.setCommand(1, turnOffCommand);

remote.pressButton(0); // 输出: Light is ON
remote.pressButton(1); // 输出: Light is OFF
remote.pressUndo();    // 输出: Light is ON
remote.pressUndo();    // 输出: Light is OFF

实际应用场景

场景1:文本编辑器的撤销/重做功能

在文本编辑器中,每一步操作都可以看作是一个命令,这样可以轻松实现撤销和重做功能。

javascript
// 文本编辑器接收者
class TextEditor {
  constructor() {
    this.content = '';
  }

  insertText(text) {
    this.content += text;
    console.log(`Inserted text: ${text}`);
    console.log(`Current content: ${this.content}`);
  }

  deleteText(length) {
    const deleted = this.content.slice(-length);
    this.content = this.content.slice(0, -length);
    console.log(`Deleted text: ${deleted}`);
    console.log(`Current content: ${this.content}`);
  }

  getContent() {
    return this.content;
  }
}

// 抽象命令类
class TextCommand {
  execute() {}
  undo() {}
}

// 插入文本命令
class InsertTextCommand extends TextCommand {
  constructor(editor, text) {
    super();
    this.editor = editor;
    this.text = text;
    this.previousContent = '';
  }

  execute() {
    this.previousContent = this.editor.getContent();
    this.editor.insertText(this.text);
  }

  undo() {
    this.editor.content = this.previousContent;
    console.log(`Undo insert: ${this.text}`);
    console.log(`Restored content: ${this.editor.getContent()}`);
  }
}

// 删除文本命令
class DeleteTextCommand extends TextCommand {
  constructor(editor, length) {
    super();
    this.editor = editor;
    this.length = length;
    this.deletedText = '';
    this.previousContent = '';
  }

  execute() {
    this.previousContent = this.editor.getContent();
    this.deletedText = this.editor.getContent().slice(-this.length);
    this.editor.deleteText(this.length);
  }

  undo() {
    this.editor.content = this.previousContent;
    console.log(`Undo delete: ${this.deletedText}`);
    console.log(`Restored content: ${this.editor.getContent()}`);
  }
}

// 命令管理器
class CommandManager {
  constructor() {
    this.history = [];
    this.undoStack = [];
  }

  executeCommand(command) {
    command.execute();
    this.history.push(command);
    this.undoStack = []; // 清空重做栈
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undoStack.push(command);
    }
  }

  redo() {
    const command = this.undoStack.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
  }
}

// 使用示例
const editor = new TextEditor();
const commandManager = new CommandManager();

const insertCmd1 = new InsertTextCommand(editor, 'Hello ');
const insertCmd2 = new InsertTextCommand(editor, 'World!');
const deleteCmd = new DeleteTextCommand(editor, 6);

commandManager.executeCommand(insertCmd1);  // 插入 "Hello "
commandManager.executeCommand(insertCmd2);  // 插入 "World!"
commandManager.executeCommand(deleteCmd);   // 删除最后6个字符

console.log('--- Undo Operations ---');
commandManager.undo();  // 撤销删除
commandManager.undo();  // 撤销插入 "World!"

console.log('--- Redo Operations ---');
commandManager.redo();  // 重做插入 "World!"

场景2:餐厅点餐系统

在餐厅点餐系统中,服务员(调用者)接受顾客的订单(命令),然后传给厨房(接收者)处理。

javascript
// 厨房接收者
class Kitchen {
  prepareBurger() {
    console.log('Preparing burger...');
    return 'Burger';
  }

  prepareFries() {
    console.log('Preparing fries...');
    return 'Fries';
  }

  prepareDrink() {
    console.log('Preparing drink...');
    return 'Drink';
  }
}

// 订单命令接口
class OrderCommand {
  execute() {}
  getItemName() {}
}

// 汉堡订单命令
class BurgerOrder extends OrderCommand {
  constructor(kitchen) {
    super();
    this.kitchen = kitchen;
  }

  execute() {
    return this.kitchen.prepareBurger();
  }

  getItemName() {
    return 'Burger';
  }
}

// 薯条订单命令
class FriesOrder extends OrderCommand {
  constructor(kitchen) {
    super();
    this.kitchen = kitchen;
  }

  execute() {
    return this.kitchen.prepareFries();
  }

  getItemName() {
    return 'Fries';
  }
}

// 饮料订单命令
class DrinkOrder extends OrderCommand {
  constructor(kitchen) {
    super();
    this.kitchen = kitchen;
  }

  execute() {
    return this.kitchen.prepareDrink();
  }

  getItemName() {
    return 'Drink';
  }
}

// 服务员 - 调用者
class Waiter {
  constructor() {
    this.orders = [];
    this.completedOrders = [];
  }

  takeOrder(order) {
    this.orders.push(order);
    console.log(`Order taken: ${order.getItemName()}`);
  }

  sendOrdersToKitchen() {
    console.log('\nSending orders to kitchen:');
    this.orders.forEach((order, index) => {
      const item = order.execute();
      this.completedOrders.push(item);
      console.log(`${index + 1}. ${item} prepared`);
    });
    this.orders = [];
  }

  serveCompletedOrders() {
    console.log('\nServing completed orders:');
    this.completedOrders.forEach((item, index) => {
      console.log(`${index + 1}. Serving ${item}`);
    });
    this.completedOrders = [];
  }
}

// 使用示例
const kitchen = new Kitchen();
const waiter = new Waiter();

// 顾客下单
waiter.takeOrder(new BurgerOrder(kitchen));
waiter.takeOrder(new FriesOrder(kitchen));
waiter.takeOrder(new DrinkOrder(kitchen));

// 发送到厨房制作
waiter.sendOrdersToKitchen();

// 上菜
waiter.serveCompletedOrders();

场景3:游戏中的输入控制

在游戏中,玩家的各种操作可以通过命令模式来处理,方便添加新操作和实现回放功能。

javascript
// 游戏角色接收者
class GameCharacter {
  constructor(name) {
    this.name = name;
    this.position = { x: 0, y: 0 };
    this.health = 100;
  }

  move(direction) {
    switch (direction) {
      case 'up':
        this.position.y -= 1;
        break;
      case 'down':
        this.position.y += 1;
        break;
      case 'left':
        this.position.x -= 1;
        break;
      case 'right':
        this.position.x += 1;
        break;
    }
    console.log(`${this.name} moved ${direction} to position (${this.position.x}, ${this.position.y})`);
  }

  attack() {
    console.log(`${this.name} attacks!`);
  }

  defend() {
    console.log(`${this.name} defends!`);
  }

  jump() {
    console.log(`${this.name} jumps!`);
  }

  getStatus() {
    return {
      name: this.name,
      position: { ...this.position },
      health: this.health
    };
  }
}

// 游戏命令接口
class GameCommand {
  execute() {}
  undo() {}
}

// 移动命令
class MoveCommand extends GameCommand {
  constructor(character, direction) {
    super();
    this.character = character;
    this.direction = direction;
    this.previousPosition = { ...character.position };
  }

  execute() {
    this.character.move(this.direction);
  }

  undo() {
    this.character.position = this.previousPosition;
    console.log(`${this.character.name} position restored to (${this.character.position.x}, ${this.character.position.y})`);
  }
}

// 攻击命令
class AttackCommand extends GameCommand {
  constructor(character) {
    super();
    this.character = character;
  }

  execute() {
    this.character.attack();
  }

  undo() {
    console.log(`${this.character.name}'s attack cancelled`);
  }
}

// 防御命令
class DefendCommand extends GameCommand {
  constructor(character) {
    super();
    this.character = character;
  }

  execute() {
    this.character.defend();
  }

  undo() {
    console.log(`${this.character.name}'s defense cancelled`);
  }
}

// 跳跃命令
class JumpCommand extends GameCommand {
  constructor(character) {
    super();
    this.character = character;
  }

  execute() {
    this.character.jump();
  }

  undo() {
    console.log(`${this.character.name}'s jump cancelled`);
  }
}

// 输入处理器 - 调用者
class InputHandler {
  constructor() {
    this.commandHistory = [];
  }

  handleInput(command) {
    command.execute();
    this.commandHistory.push(command);
  }

  replay() {
    console.log('\n--- Replaying actions ---');
    this.commandHistory.forEach(command => {
      command.execute();
    });
  }

  undoLastAction() {
    const lastCommand = this.commandHistory.pop();
    if (lastCommand) {
      lastCommand.undo();
    }
  }
}

// 使用示例
const player = new GameCharacter('Hero');
const inputHandler = new InputHandler();

// 玩家操作序列
inputHandler.handleInput(new MoveCommand(player, 'right'));
inputHandler.handleInput(new MoveCommand(player, 'up'));
inputHandler.handleInput(new AttackCommand(player));
inputHandler.handleInput(new JumpCommand(player));
inputHandler.handleInput(new DefendCommand(player));

console.log('\nPlayer Status:', player.getStatus());

// 撤销最后一个动作
console.log('\n--- Undo Last Action ---');
inputHandler.undoLastAction();

console.log('\nPlayer Status:', player.getStatus());

// 回放所有动作
inputHandler.replay();

关键要点

  1. 解耦调用者和接收者:命令模式将发出请求的对象与执行请求的对象解耦,使得系统更加灵活。

  2. 支持撤销操作:通过在命令对象中实现 undo 方法,可以轻松实现撤销功能。

  3. 易于扩展新的命令:增加新的命令只需要创建新的命令类,符合开闭原则。

  4. 支持宏命令:可以将多个命令组合成一个复合命令,批量执行。

  5. 队列请求和日志记录:命令可以被存储在队列中,或者记录到日志中以便后续处理。

与其他模式的关系

  • 与备忘录模式结合:命令模式的撤销操作常常与备忘录模式配合使用,保存对象的状态快照。
  • 与组合模式结合:可以用组合模式来实现宏命令,将多个简单命令组合成复杂命令。
  • 与策略模式区别:虽然两者都封装了算法,但命令模式关注的是封装请求,而策略模式关注的是算法族的替换。

优缺点

优点:

  1. 降低系统的耦合度:将调用操作的对象与知道如何实现该操作的对象解耦。
  2. 容易设计一个命令队列和宏命令:因为请求是封装在命令对象中的,所以可以把这些命令放入队列中。
  3. 容易实现撤销和恢复功能:只需为命令增加 undo 和 redo 方法即可。
  4. 新增命令很容易:无需修改现有类,符合开闭原则。

缺点:

  1. 可能导致系统中有过多的具体命令类:因为每一个命令都需要设计一个具体命令类。
  2. 增加了系统的复杂性:引入了额外的类和对象,可能使系统变得复杂。

命令模式广泛应用于需要撤销操作、事务处理、GUI菜单项、线程池任务等场景,是实现松耦合架构的重要手段之一。