意圖:
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。
類圖:
Command類:是一個抽象類,類中對需要執行的命令進行聲明,一般來說要對外公佈一個execute方法用來執行命令。
ConcreteCommand類:Command類的實現類,對抽象類中聲明的方法進行實現。
Client類:最終的客戶端調用類。
以上三個類的作用應該是比較好理解的,下面我們重點說一下Invoker類和Recevier類。
Invoker類:調用者,負責調用命令。
Receiver類:接收者,負責接收命令並且執行命令。
所謂對命令的封裝,說白了,無非就是把一系列的操作寫到一個方法中,然後供客戶端調用就行了,反映到類圖上,只需要一個ConcreteCommand類和Client類就可以完成對命令的封裝,即使再進一步,爲了增加靈活性,可以再增加一個Command類進行適當地抽象,這個調用者和接收者到底是什麼作用呢?
其實大家可以換一個角度去想:假如僅僅是簡單地把一些操作封裝起來作爲一條命令供別人調用,怎麼能稱爲一種模式呢?命令模式作爲一種行爲類模式,首先要做到低耦合,耦合度低了才能提高靈活性,而加入調用者和接收者兩個角色的目的也正是爲此。當我們調用時,執行的時序首先是調用者類,然後是命令類,最後是接收者類。也就是說一條命令的執行被分成了三步,它的耦合度要比把所有的操作都封裝到一個類中要低的多,而這也正是命令模式的精髓所在:把命令的調用者與執行者分開,使雙方不必關心對方是如何操作的。
例子:
我們去餐廳吃飯,我們是通過服務員來點菜,具體是誰來做這些菜和他們什麼時候完成的這些菜,其實我們都不知道。抽象之,“菜單請求者”我們和“菜單實現者”廚師,2者之間是鬆耦合的,我們對這些菜的其他一些請求比如“撤銷,重做”等,我們也不知道是誰在做。其實這就是本文要說的Command模式。
C++代碼:
#include <iostream>
#include <list>
using namespace std;
class Barbecuer
{
public:
void BakeMutton()
{
cout<<"烤羊肉串"<<endl;
}
void BakeChickenWing()
{
cout<<"烤雞翅"<<endl;
}
};
class Command
{
protected:
Barbecuer *receiver;
public:
Command(Barbecuer *receiver) : receiver(receiver){}
virtual void ExcuteCommand() = 0;
};
class BakeMuttonCommand : public Command
{
public:
BakeMuttonCommand(Barbecuer *receiver) : Command(receiver){}
virtual void ExcuteCommand()
{
receiver->BakeMutton();
}
};
class BakeChickenWingCommand : public Command
{
public:
BakeChickenWingCommand(Barbecuer *receiver) : Command(receiver){}
virtual void ExcuteCommand()
{
receiver->BakeChickenWing();
}
};
class Waiter
{
private:
list<Command*> orders;
public:
void SetOrder(Command *command)
{
orders.push_back(command);
cout<<"增加訂單"<<endl;
}
void CancelOrder(Command *command)
{
orders.remove(command);
cout<<"取消訂單"<<endl;
}
void Notify()
{
list<Command*>::const_iterator iter;
for (iter=orders.begin(); iter!=orders.end(); ++iter)
(*iter)->ExcuteCommand();
}
};
int main(int argc, char **argv)
{
Barbecuer *boy = new Barbecuer();
Command *bBakeMuttonCommand1 = new BakeMuttonCommand(boy);
Command *bBakeMuttonCommand2 = new BakeMuttonCommand(boy);
Command *bakeChickenWingCommand = new BakeChickenWingCommand(boy);
Waiter *girl = new Waiter();
girl->SetOrder(bBakeMuttonCommand1);
girl->SetOrder(bBakeMuttonCommand2);
girl->SetOrder(bakeChickenWingCommand);
girl->CancelOrder(bBakeMuttonCommand1);
girl->Notify();
system("pause");
return 0;
}
效果與實現要點:
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()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。