0%

命令模式

命令模式是最简单和优雅的模式之一,命令模式中的命令指的是一个执行某些特定事情的指令。命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

菜单程序

假设要编写一个用户界面程序,该用户界面上至少有数十个button按钮,通过分析我们发现按下按钮之后会发生的一些事情是不变的,而具体会发生什么事情是可变的。
通过command对象的帮助,将来我们可以轻易改变这种关联。
废话不多说,上代码:


//负责往按钮上面安装命令
var setCommand = function( button, command ) {
button.onclick = function() { //执行的动作
command.execute();
}
};

//一些功能函数
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() {
    console.log('删除子菜单');
};

//将命令传入command对象中
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand (SubMenu);
var delSubMenuCommand = new DelSubMenuCommand (SubMenu);

//把command对象安装到button上
setCommand(button1, refreshMenuBarCommand);
setCommand(button2, addSubMenuCommand );
setCommand(button3, delSubMenuCommand );

JavaScript中的命令模式

以上实例代码是模拟传统面向对象语言的命令模式,然而js是一门神奇的语言,运行块不一定要封装在command.execute方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以四处传递。


var setCommand = function( button, func ) {
button.onclick = function() {
func();
}
};
var MenuBar = {
refresh: function() {
console.log(‘刷新菜单界面’);
}
};
var RefreshMenuBarCommand = function( receiver ) {
return function() {
receiver.refresh();
}
};
var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar );
setCommand( button1, refreshMenuBarCommand );

看了上面代码之后,你可能会发现,使用闭包,代码量已经减少了很多。但是,如果想准确的表达当前正在使用命令模式,或者除了执行命令之外,将来有可能还要提供撤销命令等操作。那我们最好还是把执行函数改为调用execute方法:


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);

撤销和重做

很多时候,我们需要撤销一系列命令,比如在一个下围棋程序中的毁棋,我们可以把所有执行过的下棋命令都存储在一个历史列表中,然后倒序循环来依次执行这些命令的undo操作。
然而,在某些情况下无法顺利的使用undo,比如在一个Canvas画图的程序中,这时最好的办法是清除画布,然后把刚执行的命令全部重新执行一遍,这一点同样可以利用历史列表堆栈实现。
下面以一个街头霸王的游戏为例:


var Ryu = {
attack: function() {
console.log(‘攻击’);
},
defense: function() {
console.log(‘防御’);
}
};
var makeCommand = function( receiver, state ) {
return function() {
receiver state ;
}
};
var commandStack = []; // 保存命令的堆栈
document.onkeypress = function( ev ) {
var keyCode = ev.keyCode,
command = makeCommand( Ryu, commands[ keyCode ]);
if( command ) {
command(); //执行命令
commandStack.push( command ); //将刚刚执行过的命令保存进堆栈
}
};
document.getElementById(‘reply’).onclick = function() { //点击播放录像
var command;
while( command = commandStack.shift() ) { //从堆栈里依次取出命令并执行
command();
}
};

我知道是不会有人点的,但万一有人想不开呢?