什么是命令模式?

命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,使你可以用不同的请求参数化其他对象(如队列、日志),并支持请求的撤销、排队和延迟执行

其核心思想是:将 “发出命令的对象(调用者)” 与 “执行命令的对象(接收者)” 解耦,通过一个 “命令对象” 作为中间层,封装具体的操作和参数。调用者只需触发命令,无需知道命令如何执行;接收者只需执行命令,无需知道谁发起了命令。

命令模式的核心角色

  1. 命令接口(Command):定义执行命令的统一方法(如 execute())和撤销命令的方法(如 undo())。
  2. 具体命令(ConcreteCommand):实现命令接口,持有接收者的引用,在 execute() 中调用接收者的具体操作,在 undo() 中实现反向操作。
  3. 接收者(Receiver):执行命令的实际对象,包含具体的业务逻辑(如 “开灯”“关灯”)。
  4. 调用者(Invoker):接收命令并触发执行(如按钮、遥控器),可以存储命令队列,支持批量执行或撤销。

JavaScript 实现命令模式示例:智能遥控器

以下以 “智能遥控器” 为例,实现命令模式。场景是:遥控器有多个按钮,每个按钮对应一个命令(如开灯、开空调、开电视),支持执行命令和撤销上一次命令。

代码实现
// -------------- 1. 接收者(Receiver):各种设备 --------------
// 灯光设备
class Light {
  turnOn() {
    console.log("灯光已打开");
  }

  turnOff() {
    console.log("灯光已关闭");
  }
}

// 空调设备
class AirConditioner {
  turnOn() {
    console.log("空调已打开(26℃)");
  }

  turnOff() {
    console.log("空调已关闭");
  }
}

// 电视设备
class TV {
  turnOn() {
    console.log("电视已打开");
  }

  turnOff() {
    console.log("电视已关闭");
  }
}

// -------------- 2. 命令接口(Command)--------------
class Command {
  execute() {
    throw new Error("子类必须实现 execute 方法");
  }

  undo() {
    throw new Error("子类必须实现 undo 方法");
  }
}

// -------------- 3. 具体命令(ConcreteCommand)--------------
// 开灯命令
class LightOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light; // 持有接收者(灯光)的引用
  }

  // 执行:调用灯光的 turnOn
  execute() {
    this.light.turnOn();
  }

  // 撤销:调用灯光的 turnOff(反向操作)
  undo() {
    this.light.turnOff();
  }
}

// 关灯命令
class LightOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

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

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

// 开空调命令
class AirConditionerOnCommand extends Command {
  constructor(ac) {
    super();
    this.ac = ac;
  }

  execute() {
    this.ac.turnOn();
  }

  undo() {
    this.ac.turnOff();
  }
}

// 开电视命令
class TVOnCommand extends Command {
  constructor(tv) {
    super();
    this.tv = tv;
  }

  execute() {
    this.tv.turnOn();
  }

  undo() {
    this.tv.turnOff();
  }
}

// -------------- 4. 调用者(Invoker):遥控器 --------------
class RemoteControl {
  constructor() {
    this.command = null; // 当前命令
    this.lastCommand = null; // 记录上一次执行的命令(用于撤销)
  }

  // 设置当前命令(绑定按钮与命令)
  setCommand(command) {
    this.command = command;
  }

  // 按下按钮:执行命令并记录
  pressButton() {
    if (this.command) {
      this.command.execute();
      this.lastCommand = this.command; // 保存最后执行的命令
    }
  }

  // 按下撤销按钮:撤销上一次命令
  pressUndoButton() {
    if (this.lastCommand) {
      console.log("撤销操作:");
      this.lastCommand.undo();
      this.lastCommand = null; // 撤销后清空记录(可根据需求调整)
    }
  }
}

// -------------- 5. 客户端使用 --------------
// 创建接收者(设备)
const light = new Light();
const ac = new AirConditioner();
const tv = new TV();

// 创建具体命令(绑定设备)
const lightOn = new LightOnCommand(light);
const acOn = new AirConditionerOnCommand(ac);
const tvOn = new TVOnCommand(tv);
const lightOff = new LightOffCommand(light);

// 创建调用者(遥控器)
const remote = new RemoteControl();

// 场景1:按按钮开灯
remote.setCommand(lightOn);
remote.pressButton(); // 输出:灯光已打开

// 场景2:按按钮开空调
remote.setCommand(acOn);
remote.pressButton(); // 输出:空调已打开(26℃)

// 场景3:撤销上一次操作(关空调)
remote.pressUndoButton(); // 输出:撤销操作:空调已关闭

// 场景4:按按钮开电视
remote.setCommand(tvOn);
remote.pressButton(); // 输出:电视已打开

// 场景5:关灯
remote.setCommand(lightOff);
remote.pressButton(); // 输出:灯光已关闭

// 场景6:撤销上一次操作(开灯)
remote.pressUndoButton(); // 输出:撤销操作:灯光已打开

代码说明

  1. 接收者(Light、AirConditioner 等):实现具体的业务逻辑(如 turnOnturnOff),是命令的实际执行者,但不知道谁会调用它。

  2. 命令接口(Command):定义 execute()(执行命令)和 undo()(撤销命令)方法,确保所有命令遵循统一接口。

  3. 具体命令(如 LightOnCommand)

    • 持有接收者的引用(如 light),在 execute() 中调用接收者的操作(如 light.turnOn())。
    • 在 undo() 中实现反向操作(如 light.turnOff()),使命令可撤销。
  4. 调用者(RemoteControl)

    • 通过 setCommand() 绑定命令(如给按钮分配 “开灯” 命令)。
    • pressButton() 触发命令执行,并记录最后一次命令(用于撤销)。
    • pressUndoButton() 调用上一次命令的 undo() 方法,实现撤销功能。
  5. 核心优势

    • 解耦调用者与接收者:遥控器(调用者)无需知道设备(接收者)的细节,只需触发命令;设备也无需知道谁在调用它。
    • 支持撤销与排队:通过记录命令历史,可实现撤销、重做;也可将命令放入队列,实现批量执行或延迟执行(如定时任务)。
    • 易于扩展:新增命令(如 “调节空调温度”)只需添加新的具体命令类,无需修改调用者或接收者,符合 “开闭原则”。

命令模式的常见应用场景

  • 撤销 / 重做功能:如文本编辑器的撤销操作(每个编辑动作封装为命令,记录历史以便撤销)。
  • 请求排队:如任务调度系统(将任务封装为命令,放入队列按顺序执行)。
  • 日志与事务:如数据库事务(将一系列操作封装为命令,成功则提交,失败则通过 undo 回滚)。
  • GUI 交互:如按钮点击、菜单选择(每个 UI 元素绑定一个命令,触发时执行)。

命令模式的核心是 “封装请求为对象,解耦调用与执行”,它通过命令对象将操作参数化,使系统更灵活、可扩展,尤其适合需要支持撤销、排队的场景。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐