5.2 Command命令模式

意圖:

將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。

 

類圖:


 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()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章