一、命令模式使用场景及定义
命令模式常见的使用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道请求的具体操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求的发送者和请求接受者可以解除彼此之间的耦合关系。
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分隔开。请求的一方不必知道接收请求一方的接口,更不必知道请求是怎么被接收的,以及操作是否被执行,何时被执行,以及是如何执行的。
命令模式使得请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。由于对象的生命周期在程序运行期间是一直存在的,因此可以在需要的任何时间执行。
适用场景:
1.系统需要将请求调用者和请求接收者解耦,使得二者不直接交互。
2.系统需要在不同的时刻指定请求,将请求排队和执行请求。
3.系统需要支持命令的撤销(undo)和恢复(redo)操作。
4.系统需要将一组操作组合在一起,即支持宏命令。
这些优点归根结底在于我们将命令封装成了对象,
命令模式中,命令的请求发出者不直接与命令的接收者交互,而是与命令对象交互。
命令对象一般持有接收者(java中,接收者一般作为命令对象的一个属性);javascript中接收者可以作为函数的参数传递。
通过命令对象将命令请求发出者与命令接收者以松耦合的方式联系起来,因为命令对象的生命周期几乎是永久的,除非我们主动去回收它,因此命令对象的生命周期跟请求初始发生的时间无关。可以进行命令的排队、重做、取消等操作。
二、java中的命令模式
参见博客:http://blog.csdn.net/jason0539/article/details/45110355
角色:
Command 定义命令的接口,声明execute等方法;
ConcreteConmand 实现命令接口,通常会持有接收者,并调用接收者的功能来实现相应的功能。
Receiber 接口者,命令真正的执行对象。
Invoker 通常会持有命令对象,可以持有很多得命令对象,相当于使用命令对象的入口;可以对命令对象进行排队等操作。
Clinet 创建具体的命令对象,并为其设置接收者。从这个角度上来说,称之为“装配者”更为贴切。
java命令模式的使用案例(模拟对计算机的操作有开机,关机,换台等命令)
package com.bobo.shejimoshi; //命令接收者Receiver,要是真正执行命令的对象 public class Tv { public int currentChannel=0; public void turnOn(){ System.out.println("the tv is on"); } public void turnOff(){ System.out.println("the tv is off"); } public void changeChannel(int channel){ this.currentChannel=channel; System.out.println("Now tv channel is "+channel); } }
package com.bobo.shejimoshi; //执行命令的抽象接口 public interface Command { void execute(); }
package com.bobo.shejimoshi; //命令接口的实现对象 public class CommandOn implements Command{ private Tv myTv; public CommandOn(Tv tv){ myTv=tv; } @Override public void execute() { // TODO Auto-generated method stub myTv.turnOn(); } }
package com.bobo.shejimoshi; //命令接口的实现对象 public class CommandOff implements Command{ private Tv myTv; public CommandOff(Tv tv){ myTv=tv; } @Override public void execute() { myTv.turnOff(); } }
package com.bobo.shejimoshi; //命令接口的实现对象 public class CommandChange implements Command{ private Tv myTv; private int channel; public CommandChange(Tv tv,int channel){ myTv=tv; this.channel=channel; } public void execute(){ myTv.changeChannel(channel); } }
package com.bobo.shejimoshi; //可以看做是遥控器Invoker,作为使用命令对象的入口,可以持有很多的命令对象 //根据应用场景,invoker可以进行命令的排队等 public class Control { private Command onCommand,offCommand,changeChanelCommand; public Control(Command on, Command off,Command channel){ onCommand=on; offCommand=off; changeChanelCommand=channel; } public void turnOn(){ onCommand.execute(); } public void turnOff(){ offCommand.execute(); } public void changeChannel(){ changeChanelCommand.execute(); } }
package com.bobo.shejimoshi; //测试类client, public class Client { public static void main(String[] args) { //命令接收者receiver Tv myTv=new Tv(); CommandOn on=new CommandOn(myTv); CommandOff off=new CommandOff(myTv); CommandChange chanel=new CommandChange(myTv,10); Control control=new Control(on,off,chanel); //开机 control.turnOn(); //关机 control.turnOff(); //调台 control.changeChannel(); } }
三、javascript中的命令模式
javascript作为函数作为一等公民的语言,命令模式已经融入到语言的设计中。在面向对象的语言如java中,命令模式的接收者被当做command对象的属性保存起来,同时约定执行命令的操作调用command.execute方法,在javascript中,接收者可以封闭在闭包产生的环境中,执行命令的操作可以更加简便,仅仅执行回调函数即可。并且运算块可以封装在command.execute方法中,也可以封装在普通函数中。
//命令模式的学习 var tv = (function() { var turnOn = function() { console.log('打开电视机'); }; var turnOff = function() { console.log('关闭电视机'); }; var changeChannel = function(channel) { console.log('当前电视频道为' + channel); }; return { 'turnOn': turnOn, 'turnOff': turnOff, 'changeChannel': changeChannel } })(); var TurnOnCommand = function(receiver) { return function() { receiver.turnOn(); }; }; var TurnOffCommand = function(receiver) { return function() { receiver.turnOff(); }; }; var changeChannelCommand = function(receiver, channel) { return function() { receiver.changeChannel(channel); }; }; //测试 var setCommand = function(button, func) { button.onclick = function() { func(); }; }; var button = document.getElementById('btn'); var turnOnCmd = TurnOnCommand(tv); var turnOffCmd = TurnOffCommand(tv); var changeChannelCmd = changeChannelCommand(tv, 10); setCommand(button, changeChannelCommand); //可以将命令加入队列,依次执行 var commandsQueue=[]; commandsQueue.push(changeChannelCmd); commandsQueue.push(turnOnCmd); commandsQueue.push(turnOffCmd); while(command=commandsQueue.shift()){ command(); }
当然,考虑到除了执行命令之外,将来还可能提供撤销等操作,因此最好还是把执行函数改为调用execute方法,代码如下:
//实现单例模式 var tv=(function(){ var turnOn=function(){ console.log('打开电视机'); }; var turnOff=function(){ console.log('关闭电视机'); }; var changeChanel=function(channel){ console.log('当前电视频道为:'+channel); }; return{ turnOn:turnOn, turnOff:turnOff, changeChanel:changeChanel }; })(); var TurnOnCommand=function(receiver){ return { execute:function(){ receiver.turnOn(); } }; }; var TurnOffCommand=function(receiver){ return { execute:function(){ receiver.turnOff(); } }; }; var ChangeChanelCommand=function(receiver,channel){ return { execute:function(){ receiver.changeChanel(channel); } }; }; function setCommand(btn,command){ btn.addEventListener('click',function(){ command.execute(); }); } var turnOnCmd=new TurnOnCommand(tv); var turnOffCmd=new TurnOffCommand(tv); var changeChanelCmd=new ChangeChanelCommand(tv,10); var btn=document.getElementById('btn'); setCommand(btn,changeChanelCmd);
通过实践,发现javascript中的命令模式:
执行命令的操作大多数时候不是以命令对象的方式存在,很可能就是一个回调函数,从表面看起来甚至不太注意到这是命令模式。
在自己封装的动画类中,每一次执行动画的命令操作就是一个回调函数(函数保存有命令执行者的引用,不论是通过函数参数访问到命令执行者,或者是像动画类中可通过this访问到命令执行者),通过数组将动画执行操作进行排队;
在自己封装的表单验证插件中,每一次执行验证的操作实际上就是formField对象的一个方法,在合适的时机(如表单提交时)调用该方法。