命令模式的意图一是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;二是对请求排队或记录请求日志,以及支持可撤消的操作。简略图如下:
命令模式通过对命令的封装,将命令的请求(调用者Invoker)和执行(接收者Receiver)进行了责任分离,委派给不同的对象,不仅使得调用者和执行者之间实现了解耦(命令的请求方就不需要知道接收方的接口,也不需要知道命令是如何执行的具体情况),还使得可以记录命令的执行记录,添加执行日志,使得命令的控制、执行、取消和重做变得容易。
Delphi的Action就采用了这种模式,其中Windows控件(比如按钮,菜单)是调用者,Action是命令,而接收者是Action的OnExecute事件的实现者。当然,Delphi为了实现设计期间或者运行期间控件的变灰,控件的Caption能在需要时与Action保持一致,在中间加了一个ActionLink类层次结构,采用的是桥模式,控件将控件与Action之间的通信交给ActionLink来完成,而AcionLink对象的创建则采用的是工厂方法(工厂就是控件本身),只不过它利用了GetActionLinkClass函数做得更加巧妙。而Action的具体执行采用事件的方式,又使得命令和接收者进一步解耦,这样命令就不需要知道接收者的接口,也不用维护对接收者的引用,接收者也不需要知道命令的具体细节,只需要提供一个符合要求的事件处理方法即可。
下面是示例代码:
/// <summary>
/// Command类,定义一个执行操作的接口,也可以是一个接口。
/// </summary>
public abstract class Command_Command
{
protected Command_Receiver _Receiver;
public abstract void Execute();
public Command_Receiver Receiver
{
get
{
return _Receiver;
}
set
{
_Receiver = value;
}
}
}
public class Command_ConcreateCommand : Command_Command
{
public Command_ConcreateCommand()
{
}
public override void Execute()
{
if(System.Windows.Forms.MessageBox.Show("你想执行操作么?","系统提示!",
System.Windows.Forms.MessageBoxButtons.YesNo)==System.Windows.Forms.DialogResult.Yes)
{
if(this._Receiver!=null)
{
this._Receiver.Action();
}
}
}
}
public class Command_Receiver
{
public Command_Receiver()
{
}
public void Action()
{
System.Windows.Forms.MessageBox.Show("Command Execute!");
}
}
public class Command_Invoker
{
private Command_Command command;
public Command_Invoker()
{
}
public void SetCommand(Command_Command command)
{
this.command = command;
}
public void Execute()
{
this.command.Execute();
}
}
public class Command_Client
{
public static void Test()
{
//建立具体命令
Command_ConcreateCommand command = new Command_ConcreateCommand();
//建立具体接收者
Command_Receiver r1 = new Command_Receiver();
//链接命令与接收者
command.Receiver = r1;
//创建调用者
Command_Invoker invoker = new Command_Invoker();
//设置调用者的命令子类
invoker.SetCommand(command);
//执行调用操作
invoker.Execute();
}
}
总结:
如果没有将命令的请求和执行进行责任分离并委托给不同的对象,那么请求与执行都需要在同一个对象内完成,比如Button的Click命令,但这就带来一个问题,Button的Click逻辑太复杂,因为同样是Button,不同的用户请求Click命令时执行的业务逻辑可能完全不同,而且通过Button的子类化来实现也根本不现实。所以,命令的请求和执行的责任必须分离为好,要实现分离有两种办法,一是采用回调函数(原来的Windows系统比较普遍),二是采用事件或委托(Delphi对象方法),三就是采用命令模式。1,2实际上属于同一类型的处理方式,好处是简单(后记:这种应用模式虽然比较普遍,但还有一个缺点就是页面设计者和页面逻辑实现很难分离,比如dotnet的aspx页面和对应的.cs之间的耦合由于太紧密,很难实现UI设计和UI逻辑实现的分离,silverlight的VM就是为了弥补这个缺陷.)。但缺点是没法对命令本身进行管理(执行,取消,日志,重做等)。如果命令本身不需要管理和控制,使用2比较好,现在的界面控件的命令处理基本都是采用这种方式进行。如果需要管理命令本身就要采用Command模式。
如果对接收者进行抽象,就可以实现命令和接收者的动态组合,这有点类似装饰模式,策略模式。
对命令模式的一个改进就是可以将命令中接收者引用除掉,利用事件或者委托的办法与接受者发生联系。如果再对调用者进行抽象,这样就形成了一个调用者体系和命令体系,如果需要两者的联系可再增加一个联系层,这样就有3个相对独立的层次体系,Delphi的Action模式就是这样的。这样做有利于系统的可视化设计。
后记:命令模式在现在的编程体系中,使用非常广泛,特别是在UI层与控制层或者业务逻辑层之间的解耦合方面作用非常大,当然,不好的地方是增加了系统的复杂度。silverlight编程中的mvvm模式中的vm层其实就可以看做是一个命令层,由这个层衔接M和V两层,同时这个层与页面层得衔接不再是传统的页面和.cs的关系,而是采用直接在UI中进行绑定的方式进行(传统的方式是将页面事件绑定到对应的后台.cs文件,这个文件在mvvm模式中也有,除了缺省的代码和挂接VM的代码外,已经没什么代码,如果采用动态绑定V-VM,基本就只剩下缺省代码了,因此UI设计完全不用考虑对这个文件的影响),这就使得页面设计和页面实现逻辑分离,可以很好的实现UI设计和UI逻辑开发的责任分割,从而实现设计更加专业化。副作用就是需要增加了很多类和接口。