旧游无处不堪寻
无寻处,惟有少年心
设计模式-命令

命令模式(Command Pattern)是一种行为型设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中,且能实现可撤销操作。

解释


优秀的软件设计通常会将关注点进行分离,而这往往会导致软件的分层。
最常见的例子: 一层负责用户图像界面,另一层负责业务逻辑。GUI 层负责在屏幕上渲染图形,捕获所有输入并显示用户和程序工作的结果。用户操作时,GUI 层会将工作委派给业务逻辑底层处理。
代码中的结构为: 一个 GUI 对象传递一些参数来调用一个业务逻辑对象。这个过程通常被描述为一个对象发送请求给另一个对象。

命令模式建议 GUI 对象不直接提交这些请求。你应该将请求的所有细节抽取出来组成命令类,该类中仅包含一个用于触发请求的方法。命令对象负责连接不同的 GUI 和业务逻辑对象。

下一步是让所有命令实现相同的接口。该接口通常只有一个没有任何参数的执行方法,让你能在不和具体命令类耦合的情况下使用同一请求发送者执行不同命令。

GUI 对象可以给业务层对象提供一些参数。但执行命令方法没有任何参数,所以我们如何将请求的详情发送给接收者呢?答案是: 使用数据对命令进行预先配置,或者让其能够自行获取数据。

命令成为了减少 GUI 和业务逻辑层之间耦合的中间层。

命令模式 UML


适用场景


  1. 如果你需要通过操作来参数化对象,可使用命令模式
  2. 如果你想要将操作放入队列中、操作的执行或者远程执行操作,可使用命令模式
  3. 如果你想要实现操作回滚功能,可使用命令模式

代码示例


using System;
namespace CommandPatternDemo
{
// The Command interface declares a method for executing a command.
public interface ICommand
{
void Execute();
}

// Some commands can implement simple operations on their own.
class SimpleCommand : ICommand
{
private string _payload = string.Empty;

public SimpleCommand(string payload)
{
this._payload = payload;
}

public void Execute()
{
Console.WriteLine($"SimpleCommand: See, I can do simple things like printing ({this._payload})");
}
}

// However, some commands can delegate more complex operations to other
// objects, called "receivers."
class ComplexCommand : ICommand
{
private Receiver _receiver;

// Context data, required for launching the receiver's methods.
private string _a;

private string _b;

// Complex commands can accept one or several receiver objects along
// with any context data via the constructor.
public ComplexCommand(Receiver receiver, string a, string b)
{
this._receiver = receiver;
this._a = a;
this._b = b;
}

// Commands can delegate to any methods of a receiver.
public void Execute()
{
Console.WriteLine("ComplexCommand: Complex stuff should be done by a receiver object.");
this._receiver.DoSomething(this._a);
this._receiver.DoSomethingElse(this._b);
}
}

// The Receiver classes contain some important business logic. They know how
// to perform all kinds of operations, associated with carrying out a
// request. In fact, any class may serve as a Receiver.
class Receiver
{
public void DoSomething(string a)
{
Console.WriteLine($"Receiver: Working on ({a}.)");
}

public void DoSomethingElse(string b)
{
Console.WriteLine($"Receiver: Also working on ({b}.)");
}
}

// The Invoker is associated with one or several commands. It sends a
// request to the command.
class Invoker
{
private ICommand _onStart;

private ICommand _onFinish;

// Initialize commands.
public void SetOnStart(ICommand command)
{
this._onStart = command;
}

public void SetOnFinish(ICommand command)
{
this._onFinish = command;
}

// The Invoker does not depend on concrete command or receiver classes.
// The Invoker passes a request to a receiver indirectly, by executing a
// command.
public void DoSomethingImportant()
{
Console.WriteLine("Invoker: Does anybody want something done before I begin?");
if (this._onStart is ICommand)
{
this._onStart.Execute();
}

Console.WriteLine("Invoker: ...doing something really important...");

Console.WriteLine("Invoker: Does anybody want something done after I finish?");
if (this._onFinish is ICommand)
{
this._onFinish.Execute();
}
}
}

class Program
{
static void Main(string[] args)
{
// The client code can parameterize an invoker with any commands.
Invoker invoker = new Invoker();
invoker.SetOnStart(new SimpleCommand("Say Hi!"));
Receiver receiver = new Receiver();
invoker.SetOnFinish(new ComplexCommand(receiver, "Send email", "Save report"));

invoker.DoSomethingImportant();
// output

// Invoker: Does anybody want something done before I begin?
// SimpleCommand: See, I can do simple things like printing (Say Hi!)
// Invoker: ...doing something really important...
// Invoker: Does anybody want something done after I finish?
// ComplexCommand: Complex stuff should be done by a receiver object.
// Receiver: Working on (Send email.)
// Receiver: Also working on (Save report.)
}
}
}