23. 命令模式
约 1130 字大约 4 分钟
2026-03-25
定义
命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作。
人话:命令模式就像烧烤摊点单的过程。顾客来吃烧烤,会点各种菜,比如羊肉串、鸡翅、烤茄子等,而真正干活的是烤肉师傅。
如果不用命令模式来设计,就相当于顾客直接对着烤肉师傅喊:“来10串羊肉!”、“再加两串!”、“刚才那个不要了!”……这样很容易出现问题,比如:
- 顾客说多了师傅记不住
- 临时改订单容易搞混
- 忙起来谁先点的也分不清
- 想撤销某个订单也很麻烦
这在软件中就属于请求发送者和执行者耦合过高、请求管理混乱、无法支持撤销重做。
而使用命令模式之后,就相当于烧烤店安排了一个“服务员”。顾客不再直接和烤肉师傅沟通,而是把点单交给服务员。服务员会把每一个请求写成一张“小票”(也就是命令对象),然后再交给烤肉师傅去执行。
这样一来:
- 服务员负责接收和记录请求(Invoker)
- 小票负责封装具体请求(Command)
- 烤肉师傅负责真正执行(Receiver)
如果顾客想取消某个菜,只需要把对应的小票拿回来即可;如果需要重新做,也可以根据小票再执行一次。
在软件中也是一样:命令模式把“请求”封装成一个对象,使得请求的发送者和执行者解耦。调用者不需要知道具体是谁在执行,也不需要关心实现细节,同时还可以方便地支持排队、记录日志、撤销/重做等功能,让系统更加灵活、可控。
命令模式(Command)结构图
Command类——抽象命令:
// Command(抽象命令)
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
public abstract void Execute();
}ConcreteCommand类——具体命令:
// ConcreteCommand(具体命令)
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver) : base(receiver)
{
}
public override void Execute()
{
receiver.Action();
}
}Receiver类——接收者:
// Receiver(接收者)
class Receiver
{
public void Action()
{
Console.WriteLine("执行请求!");
}
}Invoker类——调用者:
// Invoker(调用者)
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}客户端代码:
static void Main(string[] args)
{
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
Console.Read();
}第一,它能较容易地设计一个命令队列:第二,在需要的情况下,可以较容易地将命令记入日志;第三,允许接收请求的一方决定是否要否决请求。第四,可以容易地实现对请求的撤销和重做:第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
但是否是碰到类似情况就一定要实现命令模式呢?比如命令模式支持撤销/恢复操作功能,但你还不清楚是否需要这个功能时,你要不要实现命令模式?其实应该是不要实现。敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
