JavaScript设计模式七(命令模式)
定义:
命令模式是最简单和优雅的模式之一,命令模式中的命令指的是一个执行某些特定事情的指令
使用场景:
有时候需要向某些对象发送请求,但是不知道请求的接受者是谁,也不知道被请求的操作是什么。这种情况需要我们设计发送者和请求的接受者消除彼此的耦合关系。
命令模式的例子
假设我们在编写一个程序,用户界面上至少由数十按钮,因为项目负责,我们让两个程序员参与,一个写样式,一个写点击按钮后的行为,这些行为都封装在对象里面。这里对于绘制按钮的程序员来说,他根本就不知道某个按钮的作用,他只知道这些按钮会被点击,然后触发一些事情。所以这个就很适合用命令模式来把按钮和具体行为解耦合。
<body>
<button id="btn1">button1</button>
<button id="btn2">button2</button>
<button id="btn3">button3</button>
</body>
<script>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
</script>
我们上面说了,这些按钮肯定会执行一些操作,我们叫command,执行的动作我们约定调用command对象的execute方法,虽然我们不知道这些命令代表的是啥子,但是负责绘制按钮的程序员也不关心这些,只需要预留好安装命令的接口,command对象自然就知道如何正确的沟通
var setCommand = function(button, command) {
button.onclick = function() {
command.execute();
};
};
然后我们在看那个写交互button的程序员,他写了刷新界面、增加子菜单和删除子菜单这几个功能
var MenuBar = {
refresh: function() {
conosle.log('refresh');
},
};
var SubMenu = {
add: function() {
console.log('add');
},
del: function() {
console.log('del');
}
}
为了让button变得有用,我们需要把这行行为都封装到命令类里面去。
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 RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand( btn1, refreshMenuBarCommand );
setCommand( btn2, addSubMenuCommand );
setCommand( btn3, delSubMenuCommand );
这样子做我们可以看到请求的发送者和请求的接受者是耦合开的。可以随意改变,随意组合
JavaScript中的命令模式
我不知道大家看了上面的代码怎么想的,我感觉很奇怪,至少我们实际中不会这么写代码。我们的目的是给对象去了一个execute的方法,为了达到这个目的,我们引入了command对象和receiver两个角色。
var bindClick = function(button, func) {
button.click = func;
}
var MenuBar = {
refresh: function() {
conosle.log('refresh');
},
};
var SubMenu = {
add: function() {
console.log('add');
},
del: function() {
console.log('del');
}
}
bindClick(btn1, MenuBar.refresh);
bindClick(btn2, SubMenu.add);
bindClick(btn3, SubMenu.del);
上一节中的实现方式是模拟的面向对象的方式来实现的,命令模式把过程化的请求封装到了command对象的execute方法中,通过封装来把运算包装。
命令模式的由来其实是回调函数面向对象的一个替代品。
JavaScript作为函数式语言,和策略模式一样,命令模式早就融入到了JavaScript中,运算块不一定要封装到command对象中,封装到函数中也是也可以的,因为函数可以作为参数到处传递,接下来我们看如何利用闭包来实现命令模式
var setCommand = function(button, func) {
button.onclick = function() {
func();
};
};
var MenuBar = {
refresh: function() {
conosle.log('refresh');
},
};
var RefreshMenuBarCommand = function(receiver) {
return function() {
receiver.refresh();
}
}
var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(btn1, refershMenuBarCommand)
命令模式除了执行命令外,还是撤销命令、重做命令等等,所以我们把上面的代码稍微修改下
var setCommand = function(button, command) {
button.onclick = function() {
command.execute();
};
};
var RefreshMenuBarCommand = function(receiver) {
return {
execute: function() {
receiver.refresh();
}
}
}
var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(btn1, refershMenuBarCommand);
至于撤销和重做是类似的
宏命令
宏命令是指一组命令,通过宏命令,我们执行一批命令,例如我们需要点击一下app,自动把门关上,顺便打开电脑,登录QQ
我们第一步,当然是创建这一个个命令拉
var closeDoorCommand = {
execute: function(){
console.log('close door');
}
}
var openPCCommand = {
execute: function() {
console.log('open pc');
}
}
var openQQCommand = {
execute: function() {
console.log('open qq');
}
}
然后定义下宏命令,增加一个add方法把子命令添加进去,调用宏命令的execute方法时,会迭代刚才添加的子命令,并以此执行
var MacroCommand = function() {
return {
commandsList: [],
add: function(command) {
this.commandsList.push(command);
},
execute: function(){
for(var i = 0; i< this.commandsList.length; i++) {
this.commandsList[i].execute();
}
}
}
}
var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPCCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();