命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和接受者之间能够消除彼此之间的耦合关系。
实现
数据结构
receiver: Map<key, action>, 表示接受者,通过接收对应的 key 来执行指令keyMap : Map<event, key>, 表示 key 的映射, 将具体的事件映射到 key 上 ,得到对应的keymakecommand: function( recevier , key ) => commond:function, 从 recevier 中取出对应的action , 得到指令commondStack : Stack<command>, 存储命令的执行栈,方便进行撤回、回放、重做等操作。
书中用《街头霸王》游戏的回放功能的一个例子
<!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>
<button id="replay">播放录像</button>
<script>
// 接受者 | 动作集
let Ryu = {
attack: function () {
console.log("攻击");
},
defence: function () {
console.log("防御");
},
jump: function () {
console.log("跳起");
},
crouch: function () {
console.log("蹲下");
},
};
// 中间层,将接受者和命令结合起来,形成可执行命令
let makeCommand = function (receiver, state) {
return function () {
receiver[state](); // 在执行匿名函数的时候就执行里面的命令
};
};
// 命令集
let commands = {
119: "jump", // W
115: "crouch", // S
97: "defence",
100: "attack",
};
let commandStack = []; // 记录执行栈
document.onkeypress = function (ev) {
let keyCode = ev.keyCode;
let command = makeCommand(Ryu, commands[keyCode]);
if (command) {
command();
commandStack.push(command);
} else {
return;
}
};
let replayBtn = document.getElementById("replay");
replayBtn.onclick = function () {
commandStack.forEach((command) => command());
};
</script>
</body>
</html>
除了撤销,我们还可以通过操作命令栈来完成排队、组合等功能。
跟许多其他语言不同,JavaScript 可以用高阶函数非常方便地实现命令模式。命令模式在JavaScript 语言中是一种隐形的模式。