设计模式(七):命令模式
这个模式我在书上(JavaScript设计模式)的那个章节来来回回看了几遍,有两个关键点并没有说清楚,导致尽管表面上是很简单明了,但本质没有说清,以下是书上的例子。
书中淡解
例子(改前):
var carManager = { // request information requestInfo: function( model, id ){ return "The information for " + model + " with ID " + id + " is foobar"; }, // purchase the car buyVehicle: function( model, id ){ return "You have successfully purchased Item " + id + ", a " + model; }, // arrange a viewing arrangeViewing: function( model, id ){ return "You have successfully booked a viewing of " + model + " ( " + id + " ) "; } }; carManager.arrangeViewing("Ferrari", "14523" ); carManager.requestInfo("Ford Mondeo", "54323" ); carManager.requestInfo("Ford Escort", "34232" ); carManager.buyVehicle("Ford Escort", "34232" );
例子(改后,命名模式):
carManager.execute = function ( name ) { return carManager[name] && carManager[name].apply( carManager, [].slice.call(arguments, 1) ); }; carManager.execute( "arrangeViewing", "Ferrari", "14523" ); carManager.execute( "requestInfo", "Ford Mondeo", "54323" ); carManager.execute( "requestInfo", "Ford Escort", "34232" ); carManager.execute( "buyVehicle", "Ford Escort", "34232" );
我想不明白,凭什么加上这个方法,就得到了优化,哪里就解耦了。
当carManager的API改变时候,调用这些API的对象当然都需要做修改,无论是改前、或者改后的代码写法。
PS:所以感觉例子不当。
网上见解
我带着疑问,放下书本,在网上寻找资料。我发现JavaScript实现的命令模式,都是类似上面的,而其他面向对象语言的实现,就是正正经经的按照模式的类图(如下)。于是,我就奇怪了,为什么JavaScript和面向对象的例子区别这么大,按理说模式应该与实现语言无关,特别是理论部分,更没有说到一块去(大部分)。
-
客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
-
命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
-
具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执方法。
-
请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
-
接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
例子:
public interface Command { public void execute(); } public class ConcreteCommand implements Command { private Receiver receiver = null; private String state; public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } public void execute() { receiver.action(); } } public class Receiver { public void action(){ //真正执行命令操作的功能代码 } } public class Invoker { private Command command = null; public void setCommand(Command command) { this.command = command; } public void runCommand() { command.execute(); } } public class Client { public void assemble(){ //创建接收者 Receiver receiver = new Receiver(); //创建命令对象,设定它的接收者 Command command = new ConcreteCommand(receiver); //创建Invoker,把命令对象设置进去 Invoker invoker = new Invoker(); invoker.setCommand(command); } }
我的见解
首先命令(Command)模式是行为设计模式。
定义:
命令模式是将方法调用、请求或操作(Receiver类里的方法)封装到一个中间者(command类),供调用者(Invoker)调用。调用者不需要知道接收者的任何接口。
例子:
Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作。
将这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式。换句话说,本来用户(调用者)是直接调用这些命令的,如菜单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔离,基本没有关系了。
简而言之,本来是调用者直接调接收者代码的,不过Command模式在两者间加了一层中间者,中间者封装接收者动作,调用者直接调中间者。
使用场景:
1. 图形界面的菜单命令。
2. 需要支持命令的撤消(undo)。
优点:
1. 解耦了发送者和接受者之间联系。发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。
2. 它能实现Undo功能。每个具体命令都可以记住它刚刚执行的动作,并且在需要时恢复。
缺点:
滥用设计模式的带来的弊端而已。
总结
在仔细查阅资料后,关于上面面向对象语言和JavaScript的各自实现例子,我认为面向对象语言是更为准确,而JavaScript是不恰当的。JavaScript的例子根本没有做到接收者与调用者的解耦,虽然carManager.execute看起来像中间者,但调用者需要知道接收者里面的接口,这里是耦合了。所以,JavaScript实例看看就算了,并不正确。
另外有篇文中讲的,设计模式一个"通病":好象喜欢将简单的问题复杂化,喜欢在不同类中增加第三者,当然这样做有利于代码的健壮性、可维护性、还有复用性。这一点也有同感。
参考文献
1. http://www.jdon.com/designpatterns/command.htm (设计模式之Command)
2. http://www.cnblogs.com/zhenyulu/articles/69858.html (命令(Command)模式)