程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET設計模式(17):命令模式(Command Pattern)

.NET設計模式(17):命令模式(Command Pattern)

編輯:關於.NET

意圖

將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤消的操作。[GOF 《設計模式》]

結構圖

Command模式結構圖如下:

圖1 Command模式結構圖

生活中的例子

Command模式將一個請求封裝為一個對象,從而使你可以使用不同的請求對客戶進行參數化。用餐時的賬單是Command模式的一個例子。服務員接受顧客的點單,把它記在賬單上封裝。這個點單被排隊等待烹饪。注意這裡的"賬單"是不依賴於菜單的,它可以被不同的顧客使用,因此它可以添入不同的點單項目。

圖2 使用用餐例子的Command模式對象圖

Command模式解說

在眾多的設計模式中,Command模式是很簡單也很優雅的一種設計模式。Command模式它封裝的是命令,把命令發出者的責任和命令執行者的責任分開。我們知道,一個類是一組操作和相應的一些變量的集合,現在有這樣一個類Document,如下:

圖3

示意性代碼:

/**//// <summary>

/// 文檔類
/// </summary>
public class Document
{
  /**//// <summary>
  /// 顯示操作
  /// </summary>
  public void Display()
  {
    Console.WriteLine("Display");
  }
  /**//// <summary>
  /// 撤銷操作
  /// </summary>
  public void Undo()
  {
    Console.WriteLine("Undo");
  }
  /**//// <summary>
  /// 恢復操作
  /// </summary>
  public void Redo()
  {
    Console.WriteLine("Redo");
  }
}

一般情況下我們使用這個類的時候,都會這樣去寫:

class Program
{
  static void Main(string[] args)
  {
    Document doc = new Document();
    doc.Display();
    doc.Undo();
    doc.Redo();
  }
}

這樣的使用本來是沒有任何問題的,但是我們看到在這個特定的應用中,出現了Undo/Redo的操作,這時如果行為的請求者和行為的實現者之間還是呈現這樣一種緊耦合,就不太合適了。可以看到,客戶程序是依賴於具體Document的命令(方法)的,引入Command模式,需要對Document中的三個命令進行抽象,這是Command模式最有意思的地方,因為在我們看來Display(),Undo(),Redo()這三個方法都應該是Document所具有的,如果單獨抽象出來成一個命令對象,那就是把函數層面的功能提到了類的層面,有點功能分解的味道,我覺得這正是Command模式解決這類問題的優雅之處,先對命令對象進行抽象:

圖4

示意性代碼:

/**//// <summary>

/// 抽象命令
/// </summary>
public abstract class DocumentCommand
{
  Document _document;
  public DocumentCommand(Document doc)
  {
    this._document = doc;
  }
  /**//// <summary>
  /// 執行
  /// </summary>
  public abstract void Execute();
}

其他的具體命令類都繼承於該抽象類,如下:

圖5

示意性代碼:

/**//// <summary>

/// 顯示命令

/// </summary>
public class DisplayCommand : DocumentCommand
{
  public DisplayCommand(Document doc)
    : base(doc)
  {
  }
  public override void Execute()
  {
    _document.Display(); 
  }
}

/**//// <summary>

/// 撤銷命令

/// </summary>
public class UndoCommand : DocumentCommand
{
  public UndoCommand(Document doc)
    : base(doc)
  {
  }
  public override void Execute()
  {
    _document.Undo(); 
  }
}

/**//// <summary>

/// 重做命令
/// </summary>
public class RedoCommand : DocumentCommand
{
  public RedoCommand(Document doc)
    : base(doc)
  {
  }
  public override void Execute()
  {
    _document.Redo(); 
  }
}

現在還需要一個Invoker角色的類,這其實相當於一個中間角色,前面我曾經說過,使用這樣的一個中間層也是我們經常使用的手法,即把A對B的依賴轉換為A對C的依賴。如下:

圖6

示意性代碼:

/**//// <summary>

/// Invoker角色
/// </summary>
public class DocumentInvoker
{
  DocumentCommand _discmd;
  DocumentCommand _undcmd;
  DocumentCommand _redcmd;
  public DocumentInvoker(DocumentCommand discmd,DocumentCommand undcmd,DocumentCommand redcmd)
  {
    this._discmd = discmd;
    this._undcmd = undcmd;
    this._redcmd = redcmd;
  }
  public void Display()
  {
    _discmd.Execute();
  }
  public void Undo()
  {
    _undcmd.Execute();
  }
  public void Redo()
  {
    _redcmd.Execute();
  }
}

現在再來看客戶程序的調用代碼:

class Program
{
  static void Main(string[] args)
  {
    Document doc = new Document();
    DocumentCommand discmd = new DisplayCommand(doc);
    DocumentCommand undcmd = new UndoCommand(doc);
    DocumentCommand redcmd = new RedoCommand(doc);
    DocumentInvoker invoker = new DocumentInvoker(discmd,undcmd,redcmd);
    invoker.Display();
    invoker.Undo();
    invoker.Redo();
  }
}

可以看到:

1.在客戶程序中,不再依賴於Document的Display(),Undo(),Redo()命令,通過Command對這些命令進行了封裝,使用它的一個關鍵就是抽象的Command類,它定義了一個操作的接口。同時我們也可以看到,本來這三個命令僅僅是三個方法而已,但是通過Command模式卻把它們提到了類的層面,這其實是違背了面向對象的原則,但它卻優雅的解決了分離命令的請求者和命令的執行者的問題,在使用Command模式的時候,一定要判斷好使用它的時機。

2.上面的Undo/Redo只是簡單示意性的實現,如果要實現這樣的效果,需要對命令對象設置一個狀態,由命令對象可以把狀態存儲起來。

.NET中的Command模式

在ASP.NET的MVC模式中,有一種叫Front Controller的模式,它分為Handler和Command樹兩個部分,Handler處理所有公共的邏輯,接收HTTP Post或Get請求以及相關的參數並根據輸入的參數選擇正確的命令對象,然後將控制權傳遞到Command對象,由其完成後面的操作,這裡面其實就是用到了Command模式。

圖7 Front Controller 的處理程序部分結構圖

圖8 Front Controller的命令部分結構圖

Handler 類負責處理各個 Web 請求,並將確定正確的 Command 對象這一職責委派給 CommandFactory 類。當 CommandFactory 返回 Command 對象後,Handler 將調用 Command 上的 Execute 方法來執行請求。具體的實現如下

Handler類:

/**//// <summary>

/// Handler類

/// </summary>
public class Handler : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    Command command = CommandFactory.Make(context.Request.Params);
    command.Execute(context);
  }
  public bool IsReusable
  {
    get
    {
      return true;
    }
  }
}

Command接口:

/**//// <summary>
/// Command
/// </summary>
public interface Command
{
  void Execute(HttpContext context);
}
CommandFactory類:

/**//// <summary>
/// CommandFactory
/// </summary>
public class CommandFactory
{
  public static Command Make(NameValueCollection parms)
  {
    string requestParm = parms["requestParm"];
    Command command = null;
    //根據輸入參數得到不同的Command對象
    switch (requestParm)
    {
      case "1":
        command = new FirstPortal();
        break;
      case "2":
        command = new SecondPortal();
        break;
      default:
        command = new FirstPortal();
        break;
    }
    return command;
  }
}

RedirectCommand類:

public abstract class RedirectCommand : Command
{
  //獲得Web.Config中定義的key和url鍵值對,UrlMap類詳見下載包中的代碼
  private UrlMap map = UrlMap.SoleInstance;
  protected abstract void OnExecute(HttpContext context);
  public void Execute(HttpContext context)
  {
    OnExecute(context);
    //根據key和url鍵值對提交到具體處理的頁面
    string url = String.Format("{0}?{1}", map.Map[context.Request.Url.AbsolutePath], context.Request.Url.Query);
    context.Server.Transfer(url);
  }
}

FirstPortal類:

public class FirstPortal : RedirectCommand
{
  protected override void OnExecute(HttpContext context)
  {
    //在輸入參數中加入項portalId以便頁面處理
    context.Items["portalId"] = "1";
  }
}

SecondPortal類:

public class SecondPortal : RedirectCommand
{
  protected override void OnExecute(HttpContext context)
  {
    context.Items["portalId"] = "2";
  }
}

效果及實現要點

1.Command模式的根本目的在於將“行為請求者”與“行為實現者”解耦,在面向對象語言中,常見的實現手段是“將行為抽象為對象”。

2.實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。

3.通過使用Compmosite模式,可以將多個命令封裝為一個“復合命令”MacroCommand。

4.Command模式與C#中的Delegate有些類似。但兩者定義行為接口的規范有所區別:Command以面向對象中的“接口-實現”來定義行為接口規范,更嚴格,更符合抽象原則;Delegate以函數簽名來定義行為接口規范,更靈活,但抽象能力比較弱。

5.使用命令模式會導致某些系統有過多的具體命令類。某些系統可能需要幾十個,幾百個甚至幾千個具體命令類,這會使命令模式在這樣的系統裡變得不實際。

適用性

在下面的情況下應當考慮使用命令模式:

1.使用命令模式作為"CallBack"在面向對象系統中的替代。"CallBack"講的便是先將一個函數登記上,然後在以後調用此函數。

2.需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命期。換言之,原先的請求發出者可能已經不在了,而命令對象本身仍然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在串形化之後傳送到另外一台機器上去。

3.系統需要支持命令的撤消(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供redo()方法,以供客戶端在需要時,再重新實施命令效果。

4.如果一個系統要將系統中所有的數據更新到日志裡,以便在系統崩潰時,可以根據日志裡讀回所有的數據更新命令,重新調用Execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。

總結

Command模式是非常簡單而又優雅的一種設計模式,它的根本目的在於將“行為請求者”與“行為實現者”解耦。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved