命令模式 :Command(轉自阿良.NET)

來源:互聯網
上載者:User
命令(Command)模式屬於對象的行為模式【GOF95】。命令模式又稱為行動(Action)模式或交易(Transaction)模式。命令模式把一個請求或者操作封裝到一個對象中。命令模式允許系統使用不同的請求把用戶端參數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢複功能。

命令模式是對命令的封裝。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的對象。

每一個命令都是一個操作:請求的一方發出請求要求執行一個操作;接收的一方收到請求,並執行操作。命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的。

二、 命令模式的結構

命令模式的類圖如下:

 命令模式涉及到五個角色,它們分別是:

客戶(Client)角色:建立了一個具體命令(ConcreteCommand)對象並確定其接收者。 
命令(Command)角色:聲明了一個給所有具體命令類的抽象介面。這是一個抽象角色。 
具體命令(ConcreteCommand)角色:定義一個接受者和行為之間的弱耦合;實現Execute()方法,負責調用接收考的相應操作。Execute()方法通常叫做執方法。 
要求者(Invoker)角色:負責調用命令對象執行請求,相關的方法叫做行動方法。 
接收者(Receiver)角色:負責具體實施和執行一個請求。任何一個類都可以成為接收者,實施和執行請求的方法叫做行動方法。 
 

三、 命令模式的示意性原始碼

 

// Command pattern -- Structural example   using System;  // "Command" abstract class Command {   // Fields   protected Receiver receiver;    // Constructors   public Command( Receiver receiver )   {     this.receiver = receiver;   }    // Methods   abstract public void Execute(); }  // "ConcreteCommand" class ConcreteCommand : Command {   // Constructors   public ConcreteCommand( Receiver receiver ) :     base ( receiver ) {}    // Methods   public override void Execute()   {     receiver.Action();   } }  // "Receiver" class Receiver {   // Methods   public void Action()   {     Console.WriteLine("Called Receiver.Action()");   } }  // "Invoker" class Invoker {   // Fields   private Command command;    // Methods   public void SetCommand( Command command )   {     this.command = command;   }    public void ExecuteCommand()   {     command.Execute();   } }  /**//// <summary> ///  Client test /// </summary> public class Client {   public static void Main( string[] args )   {     // Create receiver, command, and invoker     Receiver r = new Receiver();     Command c = new ConcreteCommand( r );     Invoker i = new Invoker();      // Set and execute command     i.SetCommand(c);     i.ExecuteCommand();   } }

四、 玉帝傳美猴王上天

命令模式不是新的發明,在美猴王大鬧天宮之前就有了。那時玉帝命令太白金星召美猴王上天:"金星徑入(水簾洞)當中,面南立定道:'我是西方太白金星,奉玉帝招安聖旨,下界請你上大,拜受仙錄。'"玉帝是系統的用戶端,太白金星是命令的發出者,猴王是命令的接收者,聖旨就是命令。玉帝的這一道命令就是要求猴王到上界報到。玉帝只管發出命令,而不管命令是怎樣傳達到美猴王的。太白金星負責將聖旨傳到,可是美猴王怎麼執行聖旨、何時執行聖旨是美猴王自己的事。果不然,個久美猴王就大鬧了天宮。

這個類比系統的設計如下:
 

五、 命令模式的實現

首先命令應當"重"一些還是"輕"一些。在不同的情況下,可以做不同的選擇。如果把命令設計得"輕",那麼它只是提供了一個要求者和接收者之間的耦合而己,命令代表要求者實現請求。

相反,如果把命令設計的"重",那麼它就應當實現所有的細節,包括請求所代表的操作,而不再需要接收者了。當一個系統沒有接收者時,就可以採用這種做法。

更常見的是處於最"輕"和最"重"的兩個極端之間時情況。命令類動態地決定調用哪一個接收者類。

其次是否支援undo和redo。如果一個命令類提供一個方法,比如叫unExecute(),以恢複其操作的效果,那麼命令類就可以支援undo和redo。具體命令類需要儲存狀態資訊,包括:

1. 接收者對象實際上實施請求所代表的操作; 
2. 對接收者對象所作的操作所需要的參數; 
3. 接收者類的最初的狀態。接收者必須提供適當的方法,使命令類可以通過調用這個方法,以便接收者類恢複原有狀態。 

如果只需要提供一層的undo和redo,那麼系統只需要儲存最後被執行的那個命令對象。如果需要支援多層的undo和redo,那麼系統就需要儲存曾經被執行過的命令的清單,清單能允許的最大的長度便是系統所支援的undo和redo的層數。沿著清單逆著執行清單上的命令的反命令(unExecute())便是undo;沿著清單順著執行清單上的命令便是redo。

六、 命令模式的實際應用案例

下面的代碼使用命令模式示範了一個簡單的計算機,並允許執行undo與redo。注意:"operator"在C#中是關鍵詞,所以在前面添加一個"@"將其變為標識符。

// Command pattern -- Real World example   using System; using System.Collections;  // "Command" abstract class Command {   // Methods   abstract public void Execute();   abstract public void UnExecute(); }  // "ConcreteCommand" class CalculatorCommand : Command {   // Fields   char @operator;   int operand;   Calculator calculator;    // Constructor   public CalculatorCommand( Calculator calculator,     char @operator, int operand )   {     this.calculator = calculator;     this.@operator = @operator;     this.operand = operand;   }    // Properties   public char Operator   {     set{ @operator = value; }   }    public int Operand   {     set{ operand = value; }   }    // Methods   override public void Execute()   {     calculator.Operation( @operator, operand );   }     override public void UnExecute()   {     calculator.Operation( Undo( @operator ), operand );   }    // Private helper function   private char Undo( char @operator )   {     char undo = ' ';     switch( @operator )     {       case '+': undo = '-'; break;       case '-': undo = '+'; break;       case '*': undo = '/'; break;       case '/': undo = '*'; break;     }     return undo;   } }  // "Receiver" class Calculator {   // Fields   private int total = 0;    // Methods   public void Operation( char @operator, int operand )   {     switch( @operator )     {       case '+': total += operand; break;       case '-': total -= operand; break;       case '*': total *= operand; break;       case '/': total /= operand; break;     }     Console.WriteLine( "Total = {0} (following {1} {2})",       total, @operator, operand );   } }  // "Invoker" class User {   // Fields   private Calculator calculator = new Calculator();   private ArrayList commands = new ArrayList();   private int current = 0;    // Methods   public void Redo( int levels )   {     Console.WriteLine( "---- Redo {0} levels ", levels );     // Perform redo operations     for( int i = 0; i < levels; i++ )       if( current < commands.Count - 1 )         ((Command)commands[ current++ ]).Execute();   }    public void Undo( int levels )   {     Console.WriteLine( "---- Undo {0} levels ", levels );     // Perform undo operations     for( int i = 0; i < levels; i++ )       if( current > 0 )         ((Command)commands[ --current ]).UnExecute();   }    public void Compute( char @operator, int operand )   {     // Create command operation and execute it     Command command = new CalculatorCommand(       calculator, @operator, operand );     command.Execute();      // Add command to undo list     commands.Add( command );     current++;   } }  /**//// <summary> /// CommandApp test /// </summary> public class Client {   public static void Main( string[] args )   {     // Create user and let her compute     User user = new User();      user.Compute( '+', 100 );     user.Compute( '-', 50 );     user.Compute( '*', 10 );     user.Compute( '/', 2 );      // Undo and then redo some commands     user.Undo( 4 );     user.Redo( 3 );   } }

七、 在什麼情況下應當使用命令模式

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

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

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

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

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

5、一個系統需要支援交易(Transaction)。一個交易結構封裝了一組資料更新命令。使用命令模式來實現交易結構可以使系統增加新的交易類型。

八、 使用命令模式的優點和缺點

命令允許請求的一方和接收請求的一方能夠獨立演化,從而且有以下的優點:

命令模式使新的命令很容易地被加入到系統裡。 
允許接收請求的一方決定是否要否決(Veto)請求。 
能較容易地設計-個命令隊列。 
可以容易地實現對請求的Undo和Redo。 
在需要的情況下,可以較容易地將命令記入日誌。 
命令模式把請求一個操作的對象與知道怎麼執行一個操作的對象分割開。 
命令類與其他任何別的類一樣,可以修改和推廣。 
你可以把命令對象彙總在一起,合成為合成命令。比如宏命令便是合成命令的例子。合成命令是合成模式的應用。 
由於加進新的具體命令類不影響其他的類,因此增加新的具體命令類很容易。
命令模式的缺點如下:

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.