四、玉帝傳美猴王上天
命令模式不是新的發明,在美猴王大鬧天宮之前就有了。那時玉帝命令太白金星召美猴王上天:"金星徑入(水簾洞)當中,面南立定道:'我是西方太白金星,奉玉帝招安聖旨,下界請你上大,拜受仙錄。'"玉帝是系統的客戶端,太白金星是命令的發出者,猴王是命令的接收者,聖旨就是命令。玉帝的這一道命令就是要求猴王到上界報到。玉帝只管發出命令,而不管命令是怎樣傳達到美猴王的。太白金星負責將聖旨傳到,可是美猴王怎麼執行聖旨、何時執行聖旨是美猴王自己的事。果不然,個久美猴王就大鬧了天宮。
這個模擬系統的設計如下:
五、命令模式的實現
首先命令應當"重"一些還是"輕"一些。在不同的情況下,可以做不同的選擇。如果把命令設計得"輕",那麼它只是提供了一個請求者和接收者之間的耦合而己,命令代表請求者實現請求。
相反,如果把命令設計的"重",那麼它就應當實現所有的細節,包括請求所代表的操作,而不再需要接收者了。當一個系統沒有接收者時,就可以采用這種做法。
更常見的是處於最"輕"和最"重"的兩個極端之間時情況。命令類動態地決定調用哪一個接收者類。
其次是否支持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 );
}
}