JavaScript设计模式(十四)——命令模式:解耦请求发送者与接收者

引言:为什么命令模式如此重要

命令模式是一种行为型设计模式,通过将请求封装为对象,使请求的发送者与接收者解耦。在JavaScript设计模式体系中,它扮演着连接业务逻辑与执行操作的重要桥梁角色。面对复杂的应用程序,命令模式有效解决了"谁发起请求"与"谁处理请求"之间的紧耦合问题,使系统更具灵活性和可扩展性。

命令模式的基本概念与结构

命令模式(Command)是一种行为设计模式,通过将请求封装为对象,从而允许参数化客户端队列、请求记录和撤销操作。

核心组件包括:

  • 命令接口:定义执行方法
  • 具体命令:实现命令接口,封装接收者和动作
  • 调用者:触发命令执行
  • 接收者:真正执行命令的对象
javascript 复制代码
// 命令接口
interface Command {
  execute(): void;
  undo(): void;
}

// 具体命令
class LightOnCommand implements Command {
  constructor(private light: Light) {}
  
  execute() {
    this.light.turnOn();
  }
  
  undo() {
    this.light.turnOff();
  }
}

// 调用者
class RemoteControl {
  constructor(private command: Command) {}
  
  pressButton() {
    this.command.execute();
  }
}

命令模式的优势在于解耦请求发送者和接收者,支持撤销操作、请求队列、日志记录和宏命令组合。特别适用于需要操作历史记录、撤销/重做功能或需要将请求排队执行的场景。

命令模式的JavaScript实现

命令模式通过将请求封装为对象,实现了发送者与接收者的解耦。在JavaScript中,我们首先定义命令接口:

javascript 复制代码
// 命令接口
class Command {
  execute() {}
  undo() {}
}

具体命令类封装接收者和操作:

javascript 复制代码
// 具体命令
class TextCommand extends Command {
  constructor(receiver, text) {
    super();
    this.receiver = receiver; // 接收者
    this.text = text;
    this.previousText = ''; // 用于撤销
  }
  
  execute() {
    this.previousText = this.receiver.text;
    this.receiver.append(this.text);
  }
  
  undo() {
    this.receiver.text = this.previousText;
  }
}

调用者存储并执行命令:

javascript 复制代码
// 调用者
class Invoker {
  constructor() {
    this.commands = [];
  }
  
  storeAndExecute(cmd) {
    cmd.execute();
    this.commands.push(cmd);
  }
  
  undoLast() {
    const cmd = this.commands.pop();
    cmd && cmd.undo();
  }
}

接收者执行实际操作:

javascript 复制代码
// 接收者
class TextEditor {
  constructor() {
    this.text = '';
  }
  
  append(newText) {
    this.text += newText;
  }
}

这种模式使得我们可以轻松扩展新命令,同时保持调用者与接收者之间的松耦合关系。

命令模式的实际应用场景

命令模式在JavaScript开发中有多种实用场景。在UI事件处理中,我们可以将用户操作封装为命令对象:

javascript 复制代码
class ButtonClickCommand {
  execute() { console.log('按钮被点击'); }
}

document.getElementById('myButton').addEventListener('click', () => {
  new ButtonClickCommand().execute();
});

撤销/重做功能通过维护命令历史记录实现:

javascript 复制代码
const history = [];
let index = -1;

function execute(command) {
  command.execute();
  history = [...history.slice(0, index + 1), command];
  index++;
}

function undo() {
  if (index >= 0) history[index--].undo();
}

任务队列使用命令模式管理异步操作:

javascript 复制代码
class CommandQueue {
  constructor() { this.queue = []; this.isRunning = false; }
  
  add(command) {
    this.queue.push(command);
    if (!this.isRunning) this.run();
  }
  
  async run() {
    this.isRunning = true;
    while (this.queue.length) await this.queue.shift().execute();
    this.isRunning = false;
  }
}

宏命令组合多个命令:

javascript 复制代码
class MacroCommand {
  constructor() { this.commands = []; }
  add(cmd) { this.commands.push(cmd); }
  execute() { this.commands.forEach(cmd => cmd.execute()); }
}

远程命令允许通过网络发送命令对象:

javascript 复制代码
const remoteCommand = { action: 'updateUser', params: { id: 1, name: 'John' } };
fetch('/api/command', { method: 'POST', body: JSON.stringify(remoteCommand) });

高级命令模式应用与变体

队列命令模式确保命令按顺序执行:

javascript 复制代码
class CommandQueue {
  constructor() {
    this.queue = [];
    this.isExecuting = false;
  }
  
  add(command) {
    this.queue.push(command);
    if (!this.isExecuting) this.execute();
  }
  
  async execute() {
    this.isExecuting = true;
    while (this.queue.length) {
      await this.queue.shift().execute();
    }
    this.isExecuting = false;
  }
}

事务命令模式实现原子性操作,全部成功或全部回滚:

javascript 复制代码
class TransactionCommand {
  constructor() {
    this.commands = [];
    this.backup = [];
  }
  
  add(cmd) { this.commands.push(cmd); }
  
  async execute() {
    for (const cmd of this.commands) {
      this.backup.push(cmd.state);
      await cmd.execute();
    }
  }
}

组合命令模式将多个命令组合成一个整体执行:

javascript 复制代码
class CompositeCommand {
  constructor() {
    this.commands = [];
  }
  
  add(cmd) { this.commands.push(cmd); }
  
  async execute() {
    for (const cmd of this.commands) {
      await cmd.execute();
    }
  }
}

结合观察者模式,在命令执行时通知监听者:

javascript 复制代码
class ObservableCommand {
  constructor() {
    this.observers = [];
  }
  
  addObserver(obs) { this.observers.push(obs); }
  
  notify(event) {
    this.observers.forEach(obs => obs.update(event));
  }
}

最佳实践与注意事项

命令模式适用于需要解耦请求发送者与接收者、支持撤销/重做操作或请求排队的场景。对简单直接方法调用,使用命令模式可能过度设计。

性能方面,命令模式会增加内存消耗,每个命令都需要创建对象。可考虑对象池技术或轻量级实现进行优化。

过度使用会导致类数量激增,特别是当有大量不同命令时。建议合并相似命令或使用参数化命令来减少类数量。

在现代前端框架中,命令模式常用于实现撤销/重做功能,如React的useReducer和Vue的状态管理模式:

javascript 复制代码
// React中的命令模式应用
const useCommandStack = () => {
  const [history, setHistory] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(-1);
  
  const execute = (command) => {
    const newHistory = history.slice(0, currentIndex + 1);
    newHistory.push(command);
    setHistory(newHistory);
    setCurrentIndex(newHistory.length - 1);
    command.execute(); // 执行命令
  };
  
  const undo = () => {
    if (currentIndex >= 0) {
      history[currentIndex].undo(); // 撤销命令
      setCurrentIndex(currentIndex - 1);
    }
  };
  
  return { execute, undo };
};

合理应用命令模式,能显著提升代码的可维护性和扩展性。

总结与展望

命令模式通过将请求封装为对象,实现了请求发送者与接收者的完全解耦,极大提升了系统的灵活性和可扩展性。在实际应用中,建议从小规模功能开始尝试,如撤销/重做操作、队列请求等,逐步扩展到复杂场景。

相关推荐
小茴香3534 小时前
Vue 脚手架(Vue CLI)
前端·javascript·vue.js
午安~婉4 小时前
ESLint
前端·eslint·检查
“抚琴”的人4 小时前
C#中获取程序执行时间
服务器·前端·c#
秉承初心4 小时前
Java 23种设计模式的详细解析
java·设计模式
掘金一周4 小时前
Flex 布局下文字省略不生效?原因其实很简单| 掘金一周 10.16
前端
你的电影很有趣4 小时前
lesson72:Node.js 安全实战:Crypto-Js 4.2.0 与 Express 加密体系构建指南
javascript·安全·node.js
Stringzhua4 小时前
Vue的Axios介绍【9】
前端·javascript·vue.js
渣哥4 小时前
从 READ_UNCOMMITTED 到 SERIALIZABLE:Spring 事务隔离级别全解析
javascript·后端·面试
云霄IT4 小时前
绕过Frida检测反调试的一些办法
android·javascript