設計模式筆記(15)---命令模式(行爲型)

Gof定義

將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。

動機

在軟件構建過程中,“行爲請求者”與“行爲實現者”通常呈現一種“緊耦合”。但在某些場合——比如需
要對行爲進行“記錄、撤銷/重做(undo/redo)、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將“行爲請求者”與“行爲實現者”解耦?將一組行爲抽象爲對象,可以實現二者之間的鬆耦合。

先來看個反例(版本1),假設在一個應用程序中需要用到要用到很多的一些外部的類,並且要對這些類中的操作進行撤銷、記錄等操作,如果像下面這樣實現就會很亂並且不容易實現:

public class Application
{
    public void Process()
    {
        Document doc = new Document();
        doc.ShowText();
        Graphics gra = new Graphics();
        gra.Draw();
        //需要進行undo 記錄等操作
    }
}
public class Document
{
    public void ShowText() { }
}
public class Graphics
{
    public void Draw(){ }
}

 

要滿足上面提的那些要求就需要用命令模式,命令模式結構圖如下:

2009-12-09_203507

改進後的代碼(版本2),將ShowText Draw這種行爲抽象起來放到一個接口中,接口命名爲ICommand,並且在該接口中還有一個方法簽名Undo,用來做撤銷處理,當然根據需要還可以加其他的操作。ICommand代碼如下:

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

 

Document類和Graphics類實現該接口,並提供自己的實現,代碼如下:

public class Document:ICommand
{
    public string Name { get;set;}
    public Document(string name)
    {
        Name = name;
    }
    public void Execute()
    {
        Console.WriteLine("顯示文本 "+Name);
    }
    public void Undo()
    {
        Console.WriteLine("撤銷顯示文本 "+Name);
    }
}

public class Graphics : ICommand
{
    public string Name { get;set;}
    public Graphics(string name)
    {
        Name = name;
    }
    public void Execute()
    {
        Console.WriteLine("畫圖 "+Name);
    }
    public void Undo()
    {
        Console.WriteLine("撤銷畫圖 "+Name);
    }
}

 

客戶端調用的代碼:

public class Application
{
    public Stack<ICommand> stack=new Stack<ICommand>();
    public void Show()
    {
        foreach (ICommand cmd in stack)
        {
            cmd.Execute();
        }
    }
    public void Undo()
    {
        ICommand command = stack.Pop();
        command.Undo();
    }
}

 

static void Main(string[] args)
{
    Application app = new Application();
    app.stack.Push(new Document("1"));
    app.stack.Push(new Graphics("1"));
    app.stack.Push(new Document("2"));
    app.stack.Push(new Graphics("2"));
    
    app.Show();
    app.Undo();
    Console.ReadLine();
}

上面的代碼運行結果如下:

2009-12-30_213413

從結果中可以看出,最後執行的畫圖2 被撤銷了。

在上面的代碼中,像Document、Graphics這樣的類都是實現了ICommand接口,如果項目中已經存在這樣的類然後還要將這些類去實現ICommand接口,顯然不是很合理,那麼就需要添加DocumentCommand類來進行轉化。完整代碼如下(版本3):

public class Document
{
    public void ShowText()
    {
        Console.WriteLine("顯示文本 ");
    }
}
public class Graphics
{
    public void Draw()
    {
        Console.WriteLine("畫圖 ");
    }
}
public interface ICommand
{
    void Execute();
    void Undo();
}
/// <summary>
/// 具體化的文檔命令類
/// </summary>
public class DocumentCommand : ICommand
{
    Document _doc;
    public DocumentCommand(Document doc)
    {
        _doc = doc;
    }
    public void Execute()
    {
        _doc.ShowText();
    }
    public void Undo()
    {
        Console.WriteLine("撤銷顯示文本 ");
    }
}
/// <summary>
/// 具體化的圖像命令類
/// </summary>
public class GraphicsCommand : ICommand
{
    Graphics _gra;
    public GraphicsCommand(Graphics gra)
    {
        _gra = gra;
    }
    public void Execute()
    {
        _gra.Draw();
    }
    public void Undo()
    {
        Console.WriteLine("撤銷畫圖 ");
    }
}
public class Application
{
    public Stack<ICommand> stack = new Stack<ICommand>();
    public void Show()
    {
        foreach (ICommand cmd in stack)
        {
            cmd.Execute();
        }
    }
    public void Undo()
    {
        ICommand command = stack.Pop();
        command.Undo();
    }
}
class Program
{
    static void Main(string[] args)
    {
        Application app = new Application();
        app.stack.Push(new DocumentCommand(new Document()));
        app.stack.Push(new GraphicsCommand(new Graphics()));

        app.Show();
        app.Undo();
        Console.ReadLine();
    }
}

 

在版本1中Application類中直接和Document中的方法相耦合,進過一步步改進後,Application類只和Document和Graphics的抽象耦合,達到了解耦的目的。

Command模式的幾個要點

  • Command模式的根本目的在於將“行爲請求者”與“行爲實現者” 解耦,在面嚮對象語言中,常見的實現手段是“將行爲抽象爲對象”。
  • 實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。
  • 通過使用Composite模式,可以將多個“命令”封裝爲一個“複合命令”MacroCommand。
  • Command模式與C#中的Delegate有些類似。但兩者定義行爲接口的規範有所區別:Command以面向對象中的“接口-實現”來定義行爲接口規範,更嚴格,更符合抽象原則;Delegate以函數簽名來定義行爲接口規範,更靈活,但抽象能力比較弱。
發佈了115 篇原創文章 · 獲贊 1 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章