加载中...
JavaScript设计模式 -- 6.命令模式
发表于:2021-06-06 | 分类: 前端

定义

命令模式中的命令(command)指的是一个执行某些特定事情的指令

应用场景

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,
使得发送者和请求接收者能够消除彼此间的耦合关系。

假设现在项目中有一个 Menu 组件,里面有至少数十个按钮,由于项目复杂度很高,组员们需要分工合作,一部分人完成绘制工作,一部分人完成具体的执行逻辑。
那么完成按钮的绘制工作后,如何增加 click 事件去执行具体逻辑呢?

<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button id="btn3">按钮3</button>
const btn1 = document.getElementById("btn1");
const btn2 = document.getElementById("btn2");
const btn3 = document.getElementById("btn3");

绘制的人无需关心具体逻辑,它只需要定义一个 setCommand 方法。
setCommand 函数负责往按钮上面安装命令。
可以肯定的是,点击按钮会执行某个 command 命令,执行命令的动作被约定为调用 command 对象的 execute()方法。

下面模拟传统的面向对象语言写一个命令模式

const setCommand = (button, command) => {
  btn.addEventListener("click", () => {
    command.execute();
  });
};

最后,负责编写点击按钮具体行为的人完成了预定的功能:

const MenuBar = {
  refresh() {
    console.log("刷新菜单");
  },
};

const SubMenu = {
  add() {
    console.log("增加子菜单");
  },
  del() {
    console.log("删除子菜单");
  },
};

然后,将行为封装在命令类中

class RefreshMenuBarCommand {
  constructor(receiver) {
    this.receiver = receiver;
  }

  execute() {
    this.receiver.refresh();
  }
}

class AddSubMenuCommand {
  constructor(receiver) {
    this.receiver = receiver;
  }

  execute() {
    this.receiver.add();
  }
}

class DelSubMenuCommand {
  constructor(receiver) {
    this.receiver = receiver;
  }

  execute() {
    this.receiver.del();
  }
}

最后,将命令接收者传入到 command 中,并且将 command 对象安装到 button 上

const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
const addSubMenuCommand = new AddSubMenuCommand(SubMenu);
const delSubMenuCommand = new DelSubMenuCommand(SubMenu);

setCommand(btn1, refreshMenuBarCommand);
setCommand(btn2, addSubMenuCommand);
setCommand(btn3, delSubMenuCommand);

js 中的命令模式

在 js 中,函数是一等公民。
我们完全可以不新增类

const bindClick = (button, func) => {
  button.onclick = func;
};

bindClick(btn1, MenuBar.refresh);
bindClick(btn2, SubMenu.add);
bindClick(btn3, SubMenu.del);

使用闭包实现命令模式

const setCommand = (btn, command) => {
  btn.onclick = function () {
    command.execute();
  };
};

const RefreshMenuBarCommand = (receiver) => {
  // 返回对象是方便后续扩展撤销操作
  return {
    execute: () => {
      receiver.refresh();
    },
  };
};

const refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(btn1, refreshMenuBarCommand);

命令队列
队列在动画中非常常见,当有一组动画动作时,则需要按照顺序执行动作。那么我们可以通过发布订阅来实现命令队列

宏命令
宏命令是一组命令的集合,通过执行宏命令,可一次执行一批命令。想象一下,家
里有一个万能遥控器,每天回家的时候,只要按一个特别的按钮,它就会帮我们关上房间门,顺
便打开电脑并登录 QQ。

首先创建好 command

const closeDoorCommand = {
  execute: () => {
    console.log("关门");
  },
};

const openPcCommand = {
  execute: () => {
    console.log("开机");
  },
};

const openQqCommand = {
  execute: () => {
    console.log("打开qq");
  },
};

然后定义宏任务 MacroCommand

const MacroCommand = () => {
  return {
    commandList: [],
    add: (command) => {
      this.commandList.push(command);
    },
    execute: () => {
      for (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute();
      }
    },
  };
};

var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();

宏命令是命令模式和组合模式的联合产物

小结

JavaScript 可以用高阶函数非常方便地实现命令模式。命令模式在 JavaScript 语言中是一种隐形的模式。
即编写好一个函数后,通过回调的方式传给另一个函数,并作为 callback 回调来调用。

上一篇:
JavaScript设计模式 -- 5.发布订阅模式
下一篇:
JavaScript设计模式 -- 7.组合模式
本文目录
本文目录