定义
命令模式中的命令(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 回调来调用。