[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`對象中保存一個接收者來負責真正執行客戶的請求,這種情況下命令對象是“傻瓜式”的,只負責把客戶的請求轉交給接收者來執行,這種模式的好處是請求發起者和請求接收者之間盡可能地得到了解耦。
- 智能命令:可以直接實現請求,不需要接收者的存在。沒有接收者的智能命令退化到和`策略模式`非常相近,從代碼結構上已經無法分辨它們,能分辨的只有它們意圖的不同。