命令模式
定义
将一个请求(方法调用)封装为一个对象,使发出请求的责任和执行请求的责任分割开。两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
详细描述
在我们日常开发中方法的发布者和方法执行者大多都是直接调用关系,发布者对象明显知道是谁调用了什么方法,这样的调用关系存在紧密的耦合关系,在遇到需要经常修改的发布者对象时非常的不利于维护和扩展。而命令模式中则是将发布者对象和执行者对象剥离开来,他们互相不知道对方是谁,他们之间的调用关系改为由命令对象来处理,命令对象暴露出一个统一的接口给发布者。这就是命令模式。Js中的命令模式大概可以分解为三个对象:
发布者对象:请求的发起者,不关心最后是谁执行,最后怎么执行,通过命令对象进行调用。
执行者对象:负责具体执行的对象,拥有执行命令接口。不关心也不知道谁发出的命令。
命令对象: 暴露统一接口给发布者,并且负责去调用执行者接口的对象。作用就是发布者和执行者的桥梁,负责暴露出统一的调用方法给发布者对象,并且去调用执行者对象的命令。
代码实例
下面使用命令模式实现给多个按钮添加不通功能的例子,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button id="button1">button1</button>
<button id="button2">button2</button>
<button id="button3">button3</button>
</div>
</body>
<script>
const button1 = document.getElementById('button1');
const button2 = document.getElementById('button2');
const button3 = document.getElementById('button3');
// 定义命令发布者对象 - 负责发布命令
const setCommand = (button, command) => {
button.onclick = () => command.execute();
}
// 定义命令执行者对象1 - 负责执行命令
const MenuBar1 = {
refresh() {
console.log('刷新功能')
}
}
// 定义命令执行者对象2 - 负责执行命令
const MenuBar2 = {
add() {
console.log('添加功能')
},
del() {
console.log('删除功能')
}
}
// 定义刷新命令对象
const RefreshMenuBarCommand = receiver => {
return {
// 暴露供发布者调用的统一接口
execute() {
receiver.refresh();
}
}
}
// 定义添加命令对象
const AddMenuBarCommand = receiver => {
return {
// 暴露供发布者调用的统一接口
execute() {
receiver.add();
}
}
}
// 发布刷新命令
setCommand(button1, RefreshMenuBarCommand(MenuBar1));
// 发布添加命令
setCommand(button2, AddMenuBarCommand(MenuBar2));
</script>
</html>
上述代码分别定义了命令发布者对象,命令对象和命令执行者对象,然后使用命令发布者对象根据按钮的功能发布不同的命令,最后由命令对象去调用执行者对象的命令接口。
上面可以看到我们只绑定了两个按钮,如果想要给第三个按钮发布命令,那么这时只需要添加相应的命令对象和添加对应的执行者对象即可。
宏命令
宏命令是一组命令的集合,通过执行宏命令的方式可以一次执行多个命令。下面我们就看下一个按钮是怎么执行多个命令的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button id="button1">button1</button>
</div>
</body>
<script>
const button1 = document.getElementById('button1');
// 定义命令发布者对象 - 负责发布命令
const setCommand = (button, command) => {
button.onclick = () => command.execute();
}
// 定义命令执行者对象1 - 负责执行命令
const MenuBar1 = {
refresh() {
console.log('刷新功能')
}
}
// 定义命令执行者对象2 - 负责执行命令
const MenuBar2 = {
add() {
console.log('添加功能')
},
del() {
console.log('删除功能')
}
}
// 定义命令对象
const MenuBarCommand = receiver => {
return {
// 命令队列
commandsList: [],
// 暴露供发布者调用的统一接口
execute() {
this.commandsList.forEach(element => element());
},
// 定义add方法收集命令
add(callback) {
this.commandsList.push(callback);
}
}
}
const macroCommand = MenuBarCommand();
// 使用命令对象的add方法添加执行者对象的命令
macroCommand.add(MenuBar2.add);
macroCommand.add(MenuBar2.del);
macroCommand.add(MenuBar1.refresh);
// 发布宏命令
setCommand(button1, macroCommand);
// 点击按钮后打印结果
// 添加功能
// 删除功能
// 刷新功能
</script>
</html>
上述代码改写了命令对象为其添加了add方法用于存储将要执行的命令,暴露给发布者调用的接口保持不变,所以最后只需要修改命令对象就轻易的实现了宏命令的改写。
总结
在js中因为函数也算是对象,所以使用函数就可以完整实现,同时它又和策略模式和代理模式有点相似,如果不单独拎出来就很难发现,它是一种隐形于js语言本身之中。