设计模式6—命令模式【行为型】

JS 命令模式核心是把 “请求 / 动作” 封装成独立对象,让触发者(Invoker)与执行者(Receiver)完全解耦,天然支持撤销 / 重做、命令队列、宏命令。下面从原理、角色、代码示例、应用场景与优缺点逐一说明。

一、核心定义与思想

  • 定义:将一个请求封装为对象,从而可用不同请求对客户参数化、排队、记录日志,并支持可撤销操作。
  • 核心思想:
    • 解耦:触发者只发命令,不关心执行细节。
    • 可记录 / 可撤销:命令是对象,可存历史、做 undo/redo。
    • 可组合:多个命令可打包成 “宏命令” 批量执行。

二、模式角色(5 个)

角色作用
Command(抽象命令) 接口,定义 execute() / undo()
ConcreteCommand(具体命令) 实现接口,持有 Receiver,调用其方法
Receiver(接收者) 实际执行业务逻辑的对象
Invoker(调用者) 触发命令,不直接操作 Receiver
Client(客户端) 创建 Receiver、Command,绑定关系

三、基础实现(灯控示例)

1. Receiver(接收者)

// 接收者:灯
class Light {
  turnOn() { console.log("灯已打开"); }
  turnOff() { console.log("灯已关闭"); }
}

2. Command(抽象命令)

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

3. ConcreteCommand(具体命令)

// 开灯命令
class LightOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light; // 绑定接收者
  }
  execute() { this.light.turnOn(); }
  undo() { this.light.turnOff(); }
}

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

4. Invoker(调用者:遥控器)

// 调用者:遥控器
class Remote {
  setCommand(cmd) { this.cmd = cmd; }
  press() { this.cmd.execute(); }
  pressUndo() { this.cmd.undo(); }
}

5. Client(客户端使用)

// 客户端
const light = new Light();
const onCmd = new LightOnCommand(light);
const offCmd = new LightOffCommand(light);

const remote = new Remote();

remote.setCommand(onCmd);
remote.press(); // 灯已打开

remote.setCommand(offCmd);
remote.press(); // 灯已关闭

remote.pressUndo(); // 灯已打开(撤销关灯)

 

四、进阶:支持历史记录与撤销 / 重做

// 接收者:文本编辑器
class Editor {
  constructor() { this.content = ""; }
  add(text) { this.content += text; }
  del() { this.content = this.content.slice(0, -1); }
}

// 具体命令:添加文本
class AddCommand extends Command {
  constructor(editor, text) {
    super();
    this.editor = editor;
    this.text = text;
    this.prevLen = 0;
  }
  execute() {
    this.prevLen = this.editor.content.length;
    this.editor.add(this.text);
  }
  undo() {
    this.editor.content = this.editor.content.slice(0, this.prevLen);
  }
}

// 调用者:命令管理器(支持历史)
class CommandManager {
  constructor() {
    this.history = []; // 执行历史
    this.redoStack = []; // 重做栈
  }
  execute(cmd) {
    cmd.execute();
    this.history.push(cmd);
    this.redoStack = []; // 新命令清空重做
  }
  undo() {
    if (!this.history.length) return;
    const cmd = this.history.pop();
    cmd.undo();
    this.redoStack.push(cmd);
  }
  redo() {
    if (!this.redoStack.length) return;
    const cmd = this.redoStack.pop();
    cmd.execute();
    this.history.push(cmd);
  }
}

// 使用
const editor = new Editor();
const manager = new CommandManager();

manager.execute(new AddCommand(editor, "Hello"));
console.log(editor.content); // Hello

manager.execute(new AddCommand(editor, " World"));
console.log(editor.content); // Hello World

manager.undo();
console.log(editor.content); // Hello

manager.redo();
console.log(editor.content); // Hello World

五、应用场景

  1. 编辑器:撤销 / 重做、历史记录(VS Code、Word)。
  2. GUI 按钮 / 菜单:按钮与动作解耦,同一动作绑定多入口。
  3. 任务队列:批量执行、延迟执行、事务回滚。
  4. 宏命令:组合多个命令为一个(如 “一键格式化”)。
  5. 状态同步:命令日志用于多端同步、操作回放。

六、优缺点

优点

  • ✅ 解耦:触发者与执行者分离,互不依赖。
  • ✅ 可撤销 / 重做:天然支持,只需实现 undo()
  • ✅ 易扩展:新增命令不修改旧代码,符合开闭原则。
  • ✅ 可组合:支持宏命令、批量操作。

缺点

  • ❌ 类膨胀:每个动作需一个命令类,增加代码量。
  • ❌ 复杂度上升:多一层抽象,简单场景可能过度设计。

七、总结

命令模式的本质是 **“动作对象化”,把 “做什么” 和 “怎么做” 拆开,让代码更灵活、可追溯、可回滚。在前端,凡是需要撤销 / 重做、历史记录、批量操作 ** 的场景,都可以优先考虑命令模式。

 

posted @ 2021-03-11 21:38  吴飞ff  阅读(48)  评论(0)    收藏  举报