设计模式(16) 命令模式

  • 命令模式
  • 适用场景
  • Redo & Undo
  • 命令模式的优缺点

命令模式

命令模式是对一类对象公共操做的抽象,它们具备相同的方法签名,因此具备相似操做,能够被抽象出来,成为一个抽象的“命令”对象。请求以命令的形式包裹在对象中,并传给调用对象。调用者寻找能够处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。这样实际操做的调用者就不是和一组对象打交道,它只须要依赖于这个“命令”对象的方法签名,并根据这个操做签名调用相关的方法。sql

GOF对命令模式描述为:
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests,and support undoable operations...
— Design Patterns : Elements of Reusable Object-Oriented Software设计模式

UML类图:
ide

代码示例:函数

public interface ICommand
{
    void Execute();
    Receiver Receiver { set; }
}

public class Receiver
{
    public string Name { get; private set; }
    public string Address { get; private set; }

    public void SetName()
    {
        this.Name = "Name";
    }

    public void SetAddress()
    {
        this.Name = "Address";
    }
}

public abstract class CommandBase : ICommand
{
    public Receiver Receiver { set; get; }

    public abstract void Execute();
}

public class SetAddressCommand : CommandBase
{
    public override void Execute()
    {
        base.Receiver.SetName();
    }
}

public class SetNameCommand : CommandBase
{
    public override void Execute()
    {
        base.Receiver.SetAddress();
    }
}

public class Invoker
{
    private IList<ICommand> commands = new List<ICommand>();
    public void AddCommand(ICommand command)
    {
        commands.Add(command);
    }

    public void Run()
    {
        foreach (ICommand command in commands)
        {
            command.Execute();
        }
    }
}

Client代码:this

Receiver receiver = new Receiver();
ICommand command1 = new SetNameCommand();
ICommand command2 = new SetAddressCommand();
command1.Receiver = receiver;
command2.Receiver = receiver;
Invoker invoker = new Invoker();
invoker.AddCommand(command1);
invoker.AddCommand(command2);
invoker.Run();

适用场景

  • 调用者同时与多个执行对象交互,并且每一个操做能够抽象为近似的形式。
  • 咱们须要控制调用自己的生命期,而不是调用者直截了当地进行一个调用,有可能根据须要合并、分配、疏导相关的调用。
  • 一系列相似的调用可能须要辅以Redo()或Undo()之类的特性。
  • 相似以往函数指针,须要在执行一个调用的同时告诉它须要回调那些操做。
  • 方法自己太过复杂,从整个项目重用的角度考虑,须要把方法的实现抽象为一组能够协做的对象。

Redo & Undo

再来看看如何用命令模式实现Redo和Undo,要实现Redo和Undo就须要保存执行过的命令,并经过安排这些命令的执行顺序来达到目地。
以SQL的执行为例,下面的代码定义了SQLExecute做为Receiver,CommandManager做为Invoker,InsertIntoCommand做为ConcreteCommand:设计

public interface ICommand
{
    public void Execute();
    public void Undo();
}

public class SQLExcute
{
    public void InsertInto(string id)
    {
        Console.WriteLine("插入一条数据,id:" + id);
    }

    public void Delete(string id)
    {
        Console.WriteLine("删除一条数据,id:" + id);
    }
}

public class InsertIntoCommand : ICommand
{
    private SQLExcute sqlExcute;
    private string id;
    public InsertIntoCommand(SQLExcute sqlExcute, string id)
    {
        this.sqlExcute = sqlExcute;
        this.id = id;
    }

    public void Execute()
    {
        sqlExcute.InsertInto(id);
    }

    public void Undo()
    {
        sqlExcute.Delete(id);
    }
}

public class CommandManager
{
    private Stack<ICommand> undoStacks = new Stack<ICommand>();
    private Stack<ICommand> redoStacks = new Stack<ICommand>();

    public void Execute(ICommand command)
    {
        command.Execute();
        undoStacks.Push(command);
        if (redoStacks.Count > 0)
        {
            redoStacks.Clear();
        }
    }

    public void Undo()
    {
        if (undoStacks.Count > 0)
        {
            ICommand pop = undoStacks.Pop();
            pop.Undo();
            redoStacks.Push(pop);
        }
    }

    public void Redo()
    {
        if (redoStacks.Count > 0)
        {
            ICommand pop = redoStacks.Pop();
            pop.Execute();
        }
    }
}

Client代码:指针

CommandManager manager = new CommandManager();
SQLExcute excute = new SQLExcute();
InsertIntoCommand command1 = new InsertIntoCommand(excute, "1");
InsertIntoCommand command2 = new InsertIntoCommand(excute, "2");
manager.Execute(command1);
manager.Execute(command2);

Console.WriteLine("undo------------");
manager.Undo(); 
manager.Undo();
Console.WriteLine("redo------------");
manager.Redo();
manager.Redo();

运行结果:日志

插入一条数据,id:1
插入一条数据,id:2
undo------------
删除一条数据,id:2
删除一条数据,id:1
redo------------
插入一条数据,id:1
插入一条数据,id:2

命令模式的优缺点

能够看到使用命令模式,调用者并不须要直接与实际的执行者打交道,实现了二者的解耦,此外基于命令的机制,能够方便地作一些相似Undo, Redo的扩展,具体的优势有:
优势:code

  • 命令模式将请求一个操做的对象与具体执行一个操做的对象分割开,符合开闭原则和迪米特法则
  • 能较容易的设计一个命令队列
  • 在须要的状况下,能够容易的将命令计入日志
  • 容许接收请求的一方决定是否接受请求
  • 能够容易的实现对请求的Undo,Redo
  • 因为加进新的具体命令类不影响其余的类,所以便于扩展

缺点:
命令模式也有其固有的缺点:在命令扩充至较多的数量时,便须要建立对应数量的ConcreteCommand,命令类过多,系统的维护会比较复杂。对象

参考书籍: 王翔著 《设计模式——基于C#的工程化实现及扩展》

相关文章
相关标签/搜索