[TOC]
# 命令模式
一个执行某些特定事情的指令。最常见的场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。命令模式在JavaScript语言中是一种隐形的模式。
## 传统面向对象编程语言的命令模式
```javascript
// 创建行为
var MenuBar = {
refresh: function(){
console.log('刷新');
}
};
var SubMenu = {
add: function(){
console.log('增加');
},
del: function(){
console.log('删除');
}
};
// 把行为封装在命令中
var RefreshMenuBarCommand = function(receiver){
this.receiver = receiver;
};
RefreshMenuBarCommand.prototype.execute = function(){
this.receiver.refresh();
};
var AddSubMenuCommand = function(receiver){
this.receiver = receiver;
};
AddSubMenuCommand.prototype.execute = function(){
this.receiver.add();
};
var DelSubMenuCommand = function(receiver){
this.receiver = receiver;
};
DelSubMenuCommand.prototype.execute = function(){
this.receiver.del();
};
var refreshMenuBarCommand = new RefreshMenuCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
// 执行命令
refreshMenuBarCommand.execute();
addSubMenuCommand.execute();
delSubMenuCommand.execute();
```
## JavaScript 的命令模式
```javascript
// 创建行为
var MenuBar = {
refresh: function(){
console.log('刷新');
}
}
// 创建命令
var RefreshMenuBarCommand = function(receiver){
return {
execute: function(){
receiver.refresh();
}
}
};
// 设置命令
var setCommand = function(button, command){
button.onclick = function(){
command.execute();
}
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
```
## 撤销和重做
撤销操作的实现一般是给命令对象增加一个名为`unexecude`或者`undo`的方法,在该方法里执行`execute`的反向操作。
撤销可以保留上一次操作的值,当执行撤销命令时,将值传入为最后一次的值。
重做则可以用一个队列来保存,将执行过的命令都保存在队列中,之后通过`shift`依次取出命令,再执行。
```javascript
var Ryu = {
attack: function(){...},
defense: function(){...},
...
}
// 创建命令
var makeCommand = function(receiver, state){
return function(){
receiver[state]();
}
};
// 创建行为
var commands = {
'100': 'attack',
'97': 'defense',
...
};
// 保存命令的堆栈
var commandStack = [];
document.onkeypress = function(ev){
var keyCode = ev.keyCode;
var command = makeCommand(Ryu, commands[keyCode]); // 获取命令
if(command){
command(); // 执行命名
commandStack.push(command); // 保存命令
}
};
document.getElementById('replay').onclick = function(){
var command;
while(command = commandStack.shift()){ // 从堆栈中依次去除命令并执行
command();
}
};
```
## 命令队列
一个请求需要等待上一个请求结束后再进行执行,此时就需要把命令放入队列中。可以尝试通过`发布-订阅`模式来配合实现。
## 宏命令
宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。
定义宏命令`MacroCommand`,它的结构也很简单。`macroCommand.add`方法表示把子命令添加进宏命令对象,当调用宏命令对象的`execute`方法时,会迭代这一组子命令对象,并且依次执行它们的`execute`方法
```javascript
var MacroCommand = function(){
return {
commandList: [], // 命令堆栈
add: function(command){ // 添加命令
this.commandList.push(command);
},
execute: function(){ // 执行命令
for(var i=0, command; command = this.commandList(i++)){
command.execute();
}
}
};
};
var macroCommand = MacroCommand();
macroCommand.add(command1);
macroCommand.add(command2);
macroCommand.execute();
```
## 智能命令与傻瓜命令
- 傻瓜命令:命名模式都会在`command`对象中保存一个接收者来负责真正执行客户的请求,这种情况下命令对象是“傻瓜式”的,只负责把客户的请求转交给接收者来执行,这种模式的好处是请求发起者和请求接收者之间尽可能地得到了解耦。
- 智能命令:可以直接实现请求,不需要接收者的存在。没有接收者的智能命令退化到和`策略模式`非常相近,从代码结构上已经无法分辨它们,能分辨的只有它们意图的不同。