概述
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合[李建忠]。这就是本文要说的Command模式。
意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。[GOF 《设计模式》]
结构图
Command模式结构图如下:
图1 Command模式结构图
生活中的例子
Command模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。用餐时的账单是Command模式的一个例子。服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。注意这里的"账单"是不依赖于菜单的,它可以被不同的顾客使用,因此它可以添入不同的点单项目。
图2 使用用餐例子的Command模式对象图
Command模式解说
在众多的设计模式中,Command模式是很简单也很优雅的一种设计模式。Command模式它封装的是命令,把命令发出者的责任和命令执行者的责任分开。我们知道,一个类是一组操作和相应的一些变量的集合,现在有这样一个类Document,如下:
图3
示意性代码:
/// 文档类
/// </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");
}
}
一般情况下我们使用这个类的时候,都会这样去写:
{
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>
public abstract class DocumentCommand
{
Document _document;
public DocumentCommand(Document doc)
{
this._document = doc;
}
/**//// <summary>
/// 执行
/// </summary>
public abstract void Execute();
}
其他的具体命令类都继承于该抽象类,如下:
图5
示意性代码:
/// 显示命令
/// </summary>
public class DisplayCommand : DocumentCommand
{
public DisplayCommand(Document doc)