命令模式的例子——菜单程序
使用面向对象的方式
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 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('删除子菜单');
};
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand(button1, refreshMenuBarCommand);
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);
用闭包实现的命令模式
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);
撤销命令
var ball = document.getElementById('ball');
var pos = document.getElementById('pos');
var moveBtn = document.getElementById('moveBtn');
var cancelBtn = document.getElementById('cancelBtn');
var MoveCommand = function (receiver, pos) {
this.receiver = receiver;
this.pos = pos;
this.oldPos = null;
};
MoveCommand.prototype.execute = function () {
this.receiver.start('left', this.pos, 1000, 'strongEaseOut');
this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName];
// 记录小球开始移动前的位置
};
MoveCommand.prototype.undo = function () {
this.receiver.start('left', this.oldPos, 1000, 'strongEaseOut');
// 回到小球移动前记录的位置
};
var moveCommand;
moveBtn.onclick = function () {
var animate = new Animate(ball);
moveCommand = new MoveCommand(animate, pos.value);
};
moveCommand.execute();
cancelBtn.onclick = function () {
moveCommand.undo(); // 撤销命令
};
撤消和重做
上一节我们讨论了如何撤销一个命令。很多时候,我们需要撤销一系列的命令。比如在一个围棋程序中,现在已经下了10步棋,我们需要一次性悔棋到第5步。在这之前,我们可以把所有执行过的下棋命令都储存在一个历史列表中,然后倒序循环来依次执行这些命令的undo操作,直到循环执行到第5个命令为止。
然而,在某些情况下无法顺利地利用undo操作让对象回到execute之前的状态。比如在一个Canvas画图的程序中,画布上有一些点,我们在这些点之间画了N条曲线把这些点相互连接起来,当然这是用命令模式来实现的。但是我们却很难为这里的命令对象定义一个擦除某条曲线的undo操作,因为在Canvas画图中,擦除一条线相对不容易实现。
这时候最好的办法是先清除画布,然后把刚才执行过的命令全部重新执行一遍,这一点同样可以利用一个历史列表堆栈办到。记录命令日志,然后重复执行它们,这是逆转不可逆命令的一个好办法。
var Ryu = {
attack: function () {
console.log('攻击');
},
defense: function () {
console.log('防御');
},
jump: function () {
console.log('跳跃');
},
crouch: function () {
console.log('蹲下');
}
};
var makeCommand = function (receiver, state) { // 创建命令
return function () {
receiver[state]();
}
};
var commands = {
"119": "jump", // W
"115": "crouch", // S
"97": "defense", // A
"100": "attack" // D
};
var commandStack = []; // 保存命令的堆栈
document.onkeypress = function (ev) {
var keyCode = ev.keyCode,
command = makeCommand(Ryu, commands[keyCode]);
if (command) {
command(); // 执行命令
commandStack.push(command); // 将刚刚执行过的命令保存进堆栈
}
};
document.getElementById('replay').onclick = function () { // 点击播放录像
var command;
while (command = commandStack.shift()) { // 从堆栈里依次取出命令并执行
command();
}
};
宏命令
下面我们看看如何逐步创建一个宏命令。
- 首先,我们依然要创建好各种Command:
var closeDoorCommand = {
execute: function () {
console.log('关门');
}
};
var openPcCommand = {
execute: function () {
console.log('开电脑');
}
};
var openQQCommand = {
execute: function () {
console.log('登录QQ' );
}
};
- 接下来定义宏命令MacroCommand,它的结构也很简单。macroCommand.add方法表示把子命令添加进宏命令对象,当调用宏命令对象的execute方法时,会迭代这一组子命令对象,并且依次执行它们的execute方法:
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command);
},
execute: function () {
for (var i = 0, command; command = this.commandsList[i++];) {
command.execute();
}
}
}
};
var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();