1. 命令模式的用途
命令模式是最简单和优雅的模式之一, 模式模式中的命令(command)指的是一个执行某些特定事情的指令
常见应用场景:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
相对于过程化的请求调用, command对象拥有更长的生命周期。对象的生命周期是跟初始请求无关的,因为这个请求已经被封装在了command对象的方法中,成为了这个对象的行为。可以在程序运行的任意时刻去调用这个方法。
命令模式还支持撤销、排队等操作
2. 例子–菜单程序
设计模式的主题总是把不变的事物和变化的事物分离开来,命令模式也不例外。
模拟传统面向对象语言的命令模式实现
<body>
<button id="button1">点击按钮1</button>
<button id="button2">点击按钮2</button>
<button id="button3">点击按钮3</button>
</body>
<script>
var button1 = document.getElementById('button1'),
var button2 = document.getElementById('button2'),
var button3 = document.getElementById('button3'),
// 定义setCommand函数,setCommand函数负责往按钮上面安装命令,执行命令的动作被约定为调用command对象的execute()方法。
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(){
this.receiver.del();
}
//把命令接收者传入到command对象中,并且把command对象安装到button上面
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand(button1, refreshMenuBarCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);
</script>
复制代码
3. JavaScript中的命令模式
传统面向对象语言的命令模式实现。模式模式将过程式的请求调用封装在command对象的execute方法里,通过封装方法调用,可以把运算块包装成形。command对象可以被四处传递,所以在调用命令的时候,客户(Client)不需要关心事情是如何进行的。
命令模式的由来,其实是回调函数的一个面向对象的替代品。
JavaScript作为将函数作为一等语言的对象,跟策略模式一样,命令模式也早已融入到了JavaScript语言之中。运算块不一定要封装在command.execute方法中,也可以封装在普通函数中。函数作为一等语言,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能
在面向对象设计中,命令模式的接收者被当成command对象的属性保存起来,同样约定执行命令的操作调用 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)
复制代码
4. 撤销命令
命令模式的作用不仅是封装运算块,而且可以很方便地给命令对象增加撤销操作。
撤销操作的实现一般是给命令对象增加一个名为unexecute或者 undo的方法,在该方法里执行 execute 的方向操作。
5.撤销和重做
重做最好的办法是先清除,然后把刚才执行过的命令全部重新执行一遍,这一点同样可以利用一个历史列表堆栈办到。
6.命令队列
把请求封装成命令对象的优点:对象的生命周期几乎是永久的,除非我们主动去回收它。也就是说,命令对象的生命周期跟初始请求发生的时间无关,command对象的execute方法可以在程序运行的任何时刻执行
7. 宏命令
宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。
var closeDoorCommand = {
execute: function(){
console.lgo('关门')
}
}
var openPcCommand = {
execute: function(){
console.lgo('开电脑')
}
}
var openQQCommand = {
execute: function(){
console.lgo('登录QQ')
}
}
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();
复制代码
8.智能命令与傻瓜命令
var closeDoorCommand = {
execute: function(){
console.lgo('关门')
}
}
复制代码
closeDoorCommand 中没有包含任何receiver的信息,它本身就包揽了执行请求的行为,跟之前的命令对象都包含了一个receiver是矛盾的
一般来说,命令模式都会在command对象中保存一个接收者来负责真正执行客户的请求,这种情况下命令对象是“傻瓜式”的,它只负责把客户的请求转交给接收者来执行,这种模式的好处是请求发起者和请求接收者之间尽可能地得到了解耦。
我们也可以定义一些更“聪明”的命令对象,“聪明”的命令对象可以直接实现请求,就不再需要接收者的存在,这种“聪明”的命令对象也叫作智能命令。没有接收者的智能命令,退化到和策略模式非常相近,从代码结构上已经无法分辨它们,能分辨的只有它们意图的不同。策略模式指向的问题域更小,所有策略对象的目标总是一致的,它们只是达到这个目标的不同手段,它们的内部实现是针对“算法”而言的。而智能命令模式指向的问题域更广,command对象解决的目标更具发散性。命令模式还可以完成撤销、排队等功能
跟许多其它语言不同,JavaScript可以用高阶函数非常方便地实现命令模式。命令模式在JavaScript语言中是一种隐形的模式