Command(命令模式)

概述
在軟件系統中,“行爲請求者”與“行爲實現者”通 常呈現一種“緊耦合”。但在某些場合,比如要對行爲進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將 “行爲請求者”與“行爲實現者”解耦?將一組行爲抽象爲對象,可以實現二者之間的鬆耦合。這就是本文要說的Command模式。
意圖
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤消的操作。[GOF 《設計模式》]
結構圖
<Design Pattern>Command模式結構圖如下:
alt
圖1 Command模式結構圖
西遊記中例子:玉帝傳美猴王上天
命 令模式不是新的發明,在美猴王大鬧天宮之前就有了。那時玉帝命令太白金星召美猴王上天:"金星徑入(水簾洞)當中,面南立定道:'我是西方太白金星,奉玉 帝招安聖旨,下界請你上大,拜受仙錄。'"玉帝是系統的客戶端,太白金星是命令的發出者,猴王是命令的接收者,聖旨就是命令。玉帝的這一道命令就是要求猴 王到上界報到。玉帝只管發出命令,而不管命令是怎樣傳達到美猴王的。太白金星負責將聖旨傳到,可是美猴王怎麼執行聖旨、何時執行聖旨是美猴王自己的事。果 不然,個久美猴王就大鬧了天宮。
這個模擬系統的設計如下:
生活中的例子
Command 模式將一個請求封裝爲一個對象,從而使你可以使用不同的請求對客戶進行參數化。用餐時的賬單是Command模式的一個例子。服務員接受顧客的點單,把它 記在賬單上封裝。這個點單被排隊等待烹飪。注意這裏的"賬單"是不依賴於菜單的,它可以被不同的顧客使用,因此它可以添入不同的點單項目。
alt
圖2 使用用餐例子的Command模式對象圖
示例用例結構圖:
手機操作系統包括開機、關機、打電話、掛電話、發短信和刪除短信功能,其實這就是一個命令模式,類結構示意圖如下:
image
 
先創建接口ICommand.cs:
1
2
3
4
5
6
7
8
9
10
11
public interface ICommand
{
    /// <summary>
    /// 執行命令
    /// </summary>
    string Execute();
    /// <summary>
    /// 撤消命令
    /// </summary>
    string UnExecute();
}
 
再創建SystemCommand.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  class SystemCommand
  {
     public string StartUp()
     {
         return "手機開機";
     }
     public string ShutDown()
     {
         return "手機關機";
     }
     public string SendSMS()
     {
         return "發短信";
     }
     public string RemoveSMS()
     {
         return "刪短信";
     }
     public string CallPhone()
     {
         return "打電話";
     }
     public string RingOff()
     {
         return "掛電話";
     }
  }
 
再創建MobileSystem.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class MobileSystem :ICommand
   {
       private SystemCommand mobileCommand;
 
       public SystemCommand MobileCommand
       {
           get
           {
               return mobileCommand;
           }
           set
           {
               mobileCommand = value;
           }
       }
 
 
       public MobileSystem(SystemCommand command)
       {
           this.mobileCommand = command;
       }
 
 
       #region ICommand 成員
 
       public abstract string Execute();
 
      public abstract string UnExecute();
 
       #endregion
   }
 
再創建StartUp.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  class StartUp :MobileSystem
{
    #region MobileSystem 成員
 
    public override string  Execute()
     {
         return MobileCommand.StartUp();
     }
 
     public override string UnExecute()
     {
         return MobileCommand.ShutDown();
     }
 
     #endregion
     public StartUp(SystemCommand command)
         : base(command)
     { }
 }
 
再創建Call.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Call : MobileSystem
   {
       #region MobileSystem 成員
 
       public override string Execute()
       {
           return MobileCommand.CallPhone();
       }
 
       public override string UnExecute()
       {
           return MobileCommand.RingOff();
       }
 
       #endregion
       public Call(SystemCommand command)
           : base(command)
       { }
   }
 
再創建SMS.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SMS : MobileSystem
{
    #region MobileSystem 成員
 
    public override string Execute()
    {
        return MobileCommand.SendSMS();
    }
 
    public override string UnExecute()
    {
        return MobileCommand.RemoveSMS();
    }
 
    #endregion
    public SMS(SystemCommand command)
        : base(command)
    { }
}
 
 
再創建MobileServer.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MobileServer : ICommand
  {
     private MobileSystem mobileSystem;
 
     public MobileServer(MobileSystem system)
     {
         this.mobileSystem = system;
     }
 
     public string Execute()
     {
       return  mobileSystem.Execute();
     }
 
     public string UnExecute()
     {
         return mobileSystem.UnExecute();
     }
 
  }
 
最後再調用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public partial class Run : Form
   {
       public Run()
       {
           InitializeComponent();
       }
 
       private void btnRun_Click(object sender, EventArgs e)
       {
 
           SystemCommand command = new SystemCommand();
 
           MobileServer server = new MobileServer(new StartUp(command));
           rtbResult.AppendText(server.Execute() + "\n");
           rtbResult.AppendText(server.UnExecute() + "\n");
           server = new MobileServer(new Call(command));
           rtbResult.AppendText(server.Execute() + "\n");
           rtbResult.AppendText(server.UnExecute() + "\n");
           server = new MobileServer(new SMS(command));
           rtbResult.AppendText(server.Execute() + "\n");
           rtbResult.AppendText(server.UnExecute() + "\n");
 
       }
   }
 
看結果:
image
效果及實現要點
1.Command模式的根本目的在於將“行爲請求者”與“行爲實現者”解耦,在面嚮對象語言中,常見的實現手段是“將行爲抽象爲對象”。
2.實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。
3.通過使用Compmosite模式,可以將多個命令封裝爲一個“複合命令”MacroCommand。
4.Command模式與C#中的Delegate有些類似。但兩者定義行爲接口的規範有所區別:Command以面向對象中的“接口-實現”來定義行爲接口規範,更嚴格,更符合抽象原則;Delegate以函數簽名來定義行爲接口規範,更靈活,但抽象能力比較弱。
5.使用Command模式會導致某些系統有過多的具體命令類。某些系統可能需要幾十個,幾百個甚至幾千個具體命令類,這會使命令模式在這樣的系統裏變得不實際。
6.從活動序列上來說通常是這樣的一個過程:客戶端指定一個命令的接受者;客戶端創建一個具體的命令對象,並且告知接受者;客戶端通過調用者對象來執行具體命令;調用者對象在合適的時候發出命令的執行指令;具體命令對象調用命令接受者的方法來落實命令的執行。
7.Command模式從結構上說變化非常多,要點就是一個抽象命令接口。抽象命令接口包含兩個含義,一是把方法提升到類的層次,二是使用統一的接口來執行命令。
8.有了前面說的這個前提,我們纔可以在調用者角色中做很多事情。比如,延遲命令的執行、爲執行的命令記錄日誌、撤銷執行的命令等等。
9.在應用的過程中可以省略一些不重要的角色。比如,如果只有一個執行者或者執行的邏輯非常簡單的話,可以把執行的邏輯合併到具體命令角色中;如果我們並不需要使用調用者來做額外的功能,僅僅是希望通過命令模式來解除客戶端和接受者之間耦合的話可以省略調用者角色。
10.如果需要實現類似於宏命令的命令組可以使用組合模式來封裝具體命令。
11. 如果需要實現undo操作,那麼命令接受者通常也需要公開undo的接口。在應用中,undo操作往往不是調用一下undo方法這麼簡單,因爲一個操作執行後所改變的環境往往是複雜的。
適用性
在下面的情況下應當考慮使用命令模式:
1.使用命令模式作爲"CallBack"在面向對象系統中的替代。"CallBack"講的便是先將一個函數登記上,然後在以後調用此函數。
2.需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命期。換言之,原先的請求發出者可能已經不在了,而命令 對象本身仍然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在串形化之後傳送到另外一臺機器上去。
3.系統需要支持命令的撤消(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供redo()方法,以供客戶端在需要時,再重新實施命令效果。
4.如果一個系統要將系統中所有的數據更新到日誌裏,以便在系統崩潰時,可以根據日誌裏讀回所有的數據更新命令,重新調用Execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。
5.命令的發起人和命令的接收人有不同的生命週期。比如,下遺囑的這種行爲就是命令模式,一般來說遺囑執行的時候命令的發起人已經死亡,命令是否得到有效的執行需要靠律師去做的。
6.希望能讓命令具有對象的性質。比如,希望命令能保存以實現撤銷;希望命令能保存以實現隊列化操作。撤銷的行爲在GUI中非常常見,隊列化命令在網絡操作中也非常常見。
7.把命令提升到類的層次後我們對類行爲的擴展就會靈活很多,別的不說,我們可以把一些創建型模式和結構型模式與命令模式結合使用。
總結
1.Command模式是非常簡單而又優雅的一種設計模式,它的根本目的在於將“行爲請求者”與“行爲實現者”解耦。
2. 不要被命令模式複雜的結構所迷惑,如果你不能理解的話請思考這句話“把方法提升到類的層次的好處也就是命令模式的好處”。
3.和把狀態或算法提到類的層次的狀態模式或策略模式相比,命令模式可能會產生更多的類或對象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章