設計模式(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#的工程化實現及擴展》

相關文章
相關標籤/搜索