命令模式中的命令(command)指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。 命令模式把客人订餐的请求封装成 command
对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。
命令模式的例子——菜单程序
在大型项目开发中,这是很正常的分工。对于绘制按钮的程序员来说,他完全不知道某个按钮未来将用来做什么,可能用来刷新菜单界面,也可能用来增加一些子菜单,他只知道点击这个按钮会发生某些事情。那么当完成这个按钮的绘制之后,应该如何给它绑定 onclick 事件呢?
回想一下命令模式的应用场景:
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
我们很快可以找到在这里运用命令模式的理由:点击了按钮之后,必须向某些负责具体行为的对象发送请求,这些对象就是请求的接收者。但是目前并不知道接收者是什么对象,也不知道接收者究竟会做什么。此时我们需要借助命令对象的帮助,以便解开按钮和负责具体行为对象之间的耦合。
var button1 = document.getElementById( 'button1' );
var button2 = document.getElementById( 'button2' );
var button3 = document.getElementById( 'button3' );
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 RefreshMenuCommand = function(receiver){
this.receiver = receiver;
}
RefreshMenuCommand.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 refreshMenuCommand = new RefreshMenuCommand( MenuBar );
var addSubMenuCommand = new AddSubMenuCommand( SubMenu );
var delSubMenuCommand = new DelSubMenuCommand( SubMenu );
setCommand(button1, refreshMenuCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);
JavaScript 中的命令模式
也许我们会感到很奇怪,所谓的命令模式,看起来就是给对象的某个方法取了 execute
的名字。引入 command
对象和receiver
这两个无中生有的角色无非是把简单的事情复杂化了,即使不用什么模式,用下面寥寥几行代码就可以实现相同的功能:
var bindClick = function( button, func ){
button.onclick = func;
};
var MenuBar = {
refresh: function(){
console.log( '刷新菜单界面' );
}
};
var SubMenu = {
add: function(){
console.log( '增加子菜单' );
},
del: function(){
console.log( '删除子菜单' );
}
};
bindClick( button1, MenuBar.refresh );
命令模式将过程式的请求调用封装在 command
对象的execute
方法里,通过封装方法调用,我们可以把运算块包装成形。command
对象可以被四处传递,所以在调用命令的时候,客户(Client)
不需要关心事情是如何进行的。
命令模式的由来,其实是回调( callback )
函数的一个面向对象的替代品。
JavaScript
作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了JavaScript
语言之中。运算块不一定要封装在 command.execute
方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。
在面向对象设计中,命令模式的接收者被当成command
对象的属性保存起来,同时约定执行命令的操作调用command.execute
方法。在使用闭包的命令模式实现中,接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可。无论接收者被保存为对象的属性,还是被封闭在闭包产生的环境中,在将来执行命令的时候,接收者都能被顺利访问。用闭包实现的命令模式如下代码所示:
如果想更明确地表达当前正在使用命令模式,或者除了执行命令之外,将来有可能还要提供撤销命令等操作。那我们最好还是把执行函数改为调用 execute
方法:
var button1 = document.getElementById( 'button1' );
var setCommand = function(button, command){
button.onclick = function(){
command.execute();
}
}
var MenuBar = {
refresh: function(){
console.log( '刷新菜单目录' );
}
};
var RefreshMenuCommand = function(receiver){
return {
execute: function(){
receiver.refresh();
}
}
}
var refreshMenuCommand = new RefreshMenuCommand( MenuBar );
setCommand(button1, refreshMenuCommand);
网友评论