.NET设计模式(17):命令模式(Command Pattern)
2006-07-17 18:11:00
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://terrylee.blog.51cto.com/342737/67767 |
命令模式(Command Pattern)
——.NET设计模式系列之十七
TerryLee,2006年7月
概述
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合[李建忠]。这就是本文要说的Command模式。
意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。[GOF 《设计模式》]
结构图
Command模式结构图如下:
图1 Command模式结构图生活中的例子
Command模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。用餐时的账单是Command模式的一个例子。服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。注意这里的"账单"是不依赖于菜单的,它可以被不同的顾客使用,因此它可以添入不同的点单项目。
图2 使用用餐例子的Command模式对象图Command模式解说
在众多的设计模式中,Command模式是很简单也很优雅的一种设计模式。Command模式它封装的是命令,把命令发出者的责任和命令执行者的责任分开。我们知道,一个类是一组操作和相应的一些变量的集合,现在有这样一个类Document,如下:
图3
![]() 示意性代码:
/// <summary>![]() /// 文档类![]() /// </summary>![]() public class Document![]() { /// <summary>![]() /// 显示操作![]() /// </summary>![]() public void Display()![]() { Console.WriteLine("Display "); } ![]() /// <summary>![]() /// 撤销操作![]() /// </summary>![]() public void Undo()![]() { Console.WriteLine("Undo "); }![]() /// <summary>![]() /// 恢复操作![]() /// </summary>![]() public void Redo()![]() { Console.WriteLine("Redo "); } }一般情况下我们使用这个类的时候,都会这样去写:
class Program![]() { static void Main(string[] args)![]() { Document doc = new Document();![]() doc.Display();![]() doc.Undo();![]() doc.Redo(); } }这样的使用本来是没有任何问题的,但是我们看到在这个特定的应用中,出现了Undo/Redo的操作,这时如果行为的请求者和行为的实现者之间还是呈现这样一种紧耦合,就不太合适了。可以看到,客户程序是依赖于具体Document的命令(方法)的,引入Command模式,需要对Document中的三个命令进行抽象,这是Command模式最有意思的地方,因为在我们看来Display(),Undo(),Redo()这三个方法都应该是Document所具有的,如果单独抽象出来成一个命令对象,那就是把函数层面的功能提到了类的层面,有点功能分解的味道,我觉得这正是Command模式解决这类问题的优雅之处,先对命令对象进行抽象:
图4示意性代码:
/// <summary>![]() /// 抽象命令![]() /// </summary>![]() public abstract class DocumentCommand![]() { Document _document;![]() public DocumentCommand(Document doc)![]() { this._document = doc; }![]() /// <summary>![]() /// 执行![]() /// </summary>![]() public abstract void Execute();![]() }其他的具体命令类都继承于该抽象类,如下:
![]() 图5 示意性代码:
/// <summary>![]() /// 显示命令![]() /// </summary>![]() public class DisplayCommand : DocumentCommand![]() { public DisplayCommand(Document doc)![]() : base(doc) { ![]() }![]() public override void Execute()![]() { _document.Display(); } }![]() ![]() /// <summary>![]() /// 撤销命令![]() /// </summary>![]() public class UndoCommand : DocumentCommand![]() { public UndoCommand(Document doc)![]() : base(doc) { ![]() }![]() public override void Execute()![]() { _document.Undo(); } }![]() ![]() /// <summary>![]() /// 重做命令![]() /// </summary>![]() public class RedoCommand : DocumentCommand![]() { public RedoCommand(Document doc)![]() : base(doc) { ![]() }![]() public override void Execute()![]() { _document.Redo(); } }现在还需要一个Invoker角色的类,这其实相当于一个中间角色,前面我曾经说过,使用这样的一个中间层也是我们经常使用的手法,即把A对B的依赖转换为A对C的依赖。如下:
图6示意性代码:
/// <summary>![]() /// Invoker角色![]() /// </summary>![]() public class DocumentInvoker![]() { DocumentCommand _discmd;![]() DocumentCommand _undcmd;![]() DocumentCommand _redcmd;![]() public DocumentInvoker(DocumentCommand discmd,DocumentCommand undcmd,DocumentCommand redcmd) {![]() this._discmd = discmd;![]() this._undcmd = undcmd;![]() this._redcmd = redcmd;![]() }![]() public void Display()![]() { _discmd.Execute(); }![]() public void Undo()![]() { _undcmd.Execute(); }![]() public void Redo()![]() { _redcmd.Execute(); } }现在再来看客户程序的调用代码:
class Program![]() { static void Main(string[] args)![]() {![]() Document doc = new Document();![]() ![]() DocumentCommand discmd = new DisplayCommand(doc);![]() DocumentCommand undcmd = new UndoCommand(doc);![]() DocumentCommand redcmd = new RedoCommand(doc);![]() ![]() DocumentInvoker invoker = new DocumentInvoker(discmd,undcmd,redcmd);![]() invoker.Display();![]() invoker.Undo();![]() invoker.Redo();![]() } }可以看到:
1.在客户程序中,不再依赖于Document的Display(),Undo(),Redo()命令,通过Command对这些命令进行了封装,使用它的一个关键就是抽象的Command类,它定义了一个操作的接口。同时我们也可以看到,本来这三个命令仅仅是三个方法而已,但是通过Command模式却把它们提到了类的层面,这其实是违背了面向对象的原则,但它却优雅的解决了分离命令的请求者和命令的执行者的问题,在使用Command模式的时候,一定要判断好使用它的时机。
2.上面的Undo/Redo只是简单示意性的实现,如果要实现这样的效果,需要对命令对象设置一个状态,由命令对象可以把状态存储起来。
.NET中的Command模式
在ASP.NET的MVC模式中,有一种叫Front Controller的模式,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,这里面其实就是用到了Command模式。
图7 Front Controller 的处理程序部分结构图 图8 Front Controller的命令部分结构图Handler 类负责处理各个 Web 请求,并将确定正确的 Command 对象这一职责委派给 CommandFactory 类。当 CommandFactory 返回 Command 对象后,Handler 将调用 Command 上的 Execute 方法来执行请求。具体的实现如下
Handler类:
/// <summary>![]() /// Handler类![]() /// </summary>![]() public class Handler : IHttpHandler![]() { public void ProcessRequest(HttpContext context)![]() {![]() Command command = CommandFactory.Make(context.Request.Params);![]() command.Execute(context);![]() }![]() public bool IsReusable![]() { get![]() { return true; } } }Command接口:
/// <summary>![]() /// Command![]() /// </summary>![]() public interface Command![]() { void Execute(HttpContext context); }CommandFactory类:
![]() |



图
图







图
图
图
图