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 };
};

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

总结与展望

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

相关推荐
辻戋2 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保2 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun3 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp3 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.4 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl6 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫7 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友7 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理9 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻9 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js