第二十五章 命令模式(Command Pattern)
意图
将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化。并对请求进行排队或记录请求日志。
使用场合
行为需要扩充:将命令作为对象处理,新的行为可以扩充子类完成。
命令需要重做:需要UnDo“撤销”和ReDo“重做”。
......
意图
将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化。并对请求进行排队或记录请求日志。
使用场合
行为需要扩充:将命令作为对象处理,新的行为可以扩充子类完成。
命令需要重做:需要UnDo“撤销”和ReDo“重做”。
日志记录:命令需要日志记录。
命令排队:需要命令支持队列执行。
结构
Command:声明执行操作的接口。
ConcreteCommand:帮定一个接收者对象到接收者的相应操作以实现操作的执行。
Client:创建一个具体命令对象并设定它的接收者。
Invoker:要求该命令执行这个请求。
Receiver:知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。

效果
1.Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
2.Command对象可以向其他对象一样被操纵和扩展。
3.将多个命令装配为一个复合命令,这是命令模式和组合模式的组合使用。
4.增加新的命令很容易,因为无须改变已有的类。
缺点
1.增加了类的数目,原来可能存在于窗体菜单调用方法中的代码将被转移到一个命令中,这样可能产生和多小类。
2.运行时命令对象增多,客户必须创建这些对象并将其与命令的执行者相关联。
采用命令模式支持“取消”与“重做”功能
Command接口:
每个命令被封装成一个实现这个接口的类。Execute方法是执行命令的方法。UndoCommand方法是撤销命令的方法。RedoCommand是重做命令的方法。
如果需要操作是不可逆的则需要使用备忘录模式记录变化过程的状态来实现操作的撤销。

ICommand.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace CommandPattern.ex25_3


{
public interface ICommand

{

/**//// <summary>
/// 操作
/// </summary>
int Execute();


/**//// <summary>
/// 撤销操作
/// </summary>
int UndoCommand();


/**//// <summary>
/// 恢复操作
/// </summary>
int RedoCommand();
}
}

Command类:
该类实现ICommand接口。功能是将数值加1,并提供“撤销”与“重做”功能。

AddCmd.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace CommandPattern.ex25_3


{
public class AddCmd:ICommand

{
private bool _isexec = false;
private int _value=0;
private int _addvalue=0;

public bool IsExec

{

get
{ return _isexec; }
}

public int Value

{

get
{ return _value; }
}

public AddCmd(int v,int addv)

{
_value=v;
_addvalue = addv;
}


ICommand メンバ#region ICommand メンバ

public int Execute()

{
if (_isexec)

{
throw new Exception ("is done");
}
else

{
_value += _addvalue;
_isexec = true;
}

return _value;
}

public int UndoCommand()

{
if (!_isexec)

{
throw new Exception("not done");
}
else

{
_value -= _addvalue;
_isexec = false;
}

return _value;
}

public int RedoCommand()

{
if (_isexec)

{
throw new Exception("have done");
}
else

{
_value += _addvalue;
_isexec = true;
}

return _value;
}

#endregion
}
}

堆栈类:
用来存储命令的集合类。模拟堆栈的功能。

Commands.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace CommandPattern.ex25_3


{
class Commands:ICollection<CommandPattern.ex25_3.ICommand>

{
private List<ICommand> cmds=new List<ICommand>();


ICollection メンバ#region ICollection<ICommand> メンバ

public void Add(ICommand item)

{
cmds.Add(item);
}

public void Clear()

{
cmds.Clear();
}

public bool Contains(ICommand item)

{
return cmds.Contains(item);
}

public void CopyTo(ICommand[] array, int arrayIndex)

{
cmds.CopyTo(array, arrayIndex);
}

public int Count

{

get
{ return cmds.Count; }
}

public bool IsReadOnly

{

get
{ return false; }
}

public bool Remove(ICommand item)

{
return cmds.Remove(item);
}

#endregion


IEnumerable メンバ#region IEnumerable<ICommand> メンバ

public IEnumerator<ICommand> GetEnumerator()

{
return cmds.GetEnumerator();
}

#endregion


IEnumerable メンバ#region IEnumerable メンバ

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

{
return cmds.GetEnumerator();
}

#endregion

public CommandPattern.ex25_3.ICommand PopCommand()

{
if (cmds.Count == 0)

{
return null;
}

CommandPattern.ex25_3.ICommand icmd=cmds[cmds.Count-1];
cmds.RemoveAt(cmds.Count - 1);
return icmd;
}
}
}

测试类:
这个类的代码也可以直接写在Main方法中,这里是为了清晰才作了封装。
定义了两个堆栈,一个是记录可执行撤销操作的命令的堆栈(ReDoCommand),一个是记录可执行重做操作的命令的堆栈(UnDoCommand)。
用户循环输入命令字符,执行命令操作。

Tester.cs
using System;
using System.Collections.Generic;
using System.Text;
using CommonFrameworkWBs.ValidateTool;

namespace CommandPattern.ex25_3


{
class Tester

{
private Commands ReDoCommand;
private Commands UnDoCommand;
private int _value = 0;

public int Value

{

get
{ return _value; }
}

public Tester()

{
ReDoCommand = new Commands();
UnDoCommand = new Commands();
}

public void Test()

{
string cmd="";

Console.WriteLine("Init Value:{0}", this._value);

while (cmd!="x")

{
Console.WriteLine("ReDoCommand:{0},UnDoCommand:{1}",
ReDoCommand.Count, UnDoCommand.Count);

Console.WriteLine("[e] = Execute ,[u] = UnDo ,[r] = ReDo ,[x] = Exit ,[c] = ClearCommand");
cmd=Console.ReadLine();

switch(cmd)

{
case "e":
AddCmd();
break;
case "u":
UnDo();
break;
case "r":
ReDo();
break;
case "c":
UnDoCommand.Clear();
ReDoCommand.Clear();
break;
case"x":
break;
}
}
}

private void AddCmd()

{
ICommand icmd;

ValidateNumber vn=new ValidateNumber();
Console.WriteLine("请输入整数");
string num=Console.ReadLine();
icmd = new AddCmd(this._value, int.Parse(num));

this._value=icmd.Execute();

this.UnDoCommand.Add(icmd);

Console.WriteLine("Value:{0}", this._value);
}

private void UnDo()

{
ICommand icmd = UnDoCommand.PopCommand();

if (icmd == null)

{
Console.WriteLine("UnDoCommand is null");
return;
}

this._value = icmd.UndoCommand();

this.ReDoCommand.Add(icmd);

Console.WriteLine("Value:{0}", this._value);
}

private void ReDo()

{
ICommand icmd = ReDoCommand.PopCommand();

if (icmd == null)

{
Console.WriteLine("ReDoCommand is null");
return;
}

this._value = icmd.RedoCommand();

this.UnDoCommand.Add(icmd);

Console.WriteLine("Value:{0}", this._value);
}
}
}

测试类的使用
ex25_3.Tester t=new CommandPattern.ex25_3.Tester();
t.Test();
当然可以定义更多的命令对象,这里为了简单只定义了一个。另外命令对象可以由一个工厂来负责生产。
(转Terrylee的例子)
.NET中的Command模式
在ASP.NET的MVC模式中,有一种叫Front Controller的模式,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,这里面其实就是用到了Command模式。

图2 Front Controller 的处理程序部分结构图

Handler 类负责处理各个 Web 请求,并将确定正确的 Command 对象这一职责委派给 CommandFactory 类。当 CommandFactory 返回 Command 对象后,Handler 将调用 Command 上的 Execute 方法来执行请求。具体的实现如下
Handler类:

Handler.cs
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接口:

Command.cs
public interface Command


{
void Execute(HttpContext context);
}
CommandFactory类:

CommandFactory.cs
public class CommandFactory



{
public static Command Make(NameValueCollection parms)


{

string requestParm = parms["requestParm"];

Command command = null;

//根据输入参数得到不同的Command对象

switch (requestParm)


{
case "1":

command = new FirstPortal();

break;

case "2":

command = new SecondPortal();

break;

default:

command = new FirstPortal();

break;
}

return command;

}
}
RedirectCommand类:

RedirectCommand.cs
public abstract class RedirectCommand : Command



{
//获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码

private UrlMap map = UrlMap.SoleInstance;

protected abstract void OnExecute(HttpContext context);

public void Execute(HttpContext context)


{
OnExecute(context);

//根据key和url键值对提交到具体处理的页面

string url = String.Format("{0}?{1}", map.Map[context.Request.Url.AbsolutePath], context.Request.Url.Query);

context.Server.Transfer(url);

}
}
FirstPortal类:

FirstPortal.cs
public class FirstPortal : RedirectCommand



{
protected override void OnExecute(HttpContext context)


{
//在输入参数中加入项portalId以便页面处理

context.Items["portalId"] = "1";

}
}
SecondPortal类

SecondPortal.cs
public class SecondPortal : RedirectCommand



{
protected override void OnExecute(HttpContext context)


{
context.Items["portalId"] = "2";
}
}
相关模式
1.组合模式:可以采用组合模式来实现宏命令。
2.备忘录模式:可以采用备忘录模式来保存命令的状态。
3.装饰模式: 可以用装饰模式为命令增加记录日志的功能。
4.代理模式:可以用代理来为命令增加权限控制。