第16章 行為型模式—命令模式
1. 命令模式(Command Pattern)的定義
(1)定義
將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。
①封裝請求:抽象出需要執行的動作,封裝成物件(有統一的介面)。
②引數化:可以用不同的命令物件,去引數化配置客戶的請求。(即,將命令物件作為引數去供Invoker呼叫)。
(2)命令模式的結構和說明
①Command:定義命令的介面,宣告執行的方法
②ConcreteCommand:命令介面實現物件,是“虛”的實現,通常會持有接收者並呼叫接收者的功能來完成命令要執行的操作
③Receiver:接收者,真正執行命令的物件。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
④Invoker:要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令物件的入口。
⑤Client:建立具體的命令物件,並且設定命令物件的接收者。注意,這個不是我們常規意義上的客戶端,而是在組裝命令物件和接收者。或許,把這個Client稱為裝配者會更好,因為真正使用命令的客戶端是從Invoker來觸發執行。
【程式設計實驗】打飛機遊戲
//行為型模式:命令模式 //場景:打飛機遊戲 //角色控制器(如玩家)的主要操作:用導彈攻擊、炸彈攻擊來打飛機以及4個方向移動 #include <iostream> #include <string> #include <vector> using namespace std; //***********************************************抽象命令介面*********************************** //Command class FighterCommand { public: virtual void execute() = 0; }; //*************************************************接收者************************************** //Receiver class Fighter { public: void missile() { cout << "用導彈攻擊!" << endl; } void bomb() { cout << "用炸彈攻擊!" << endl; } void move (int direction) { switch(direction) { case 1: cout << "向上移動!" << endl; break; case 2: cout << "向下移動!" <<endl; break; case 3: cout << "向左移動!" << endl; break; case 4: cout << "向右移動!" <<endl; break; default: cout << "不移動!" << endl; } } }; //*************************************ConcreteCommand*************************************** //導彈攻擊命令 class MissileCommand : public FighterCommand { private: Fighter* fighter; public: MissileCommand(Fighter* fighter) { this->fighter = fighter; } void execute() { fighter->missile(); } }; //炸彈攻擊命令 class BombCommand : public FighterCommand { private: Fighter* fighter; public: BombCommand(Fighter* fighter) { this->fighter = fighter; } void execute() { fighter->bomb(); } }; //移動命令 class MoveCommand : public FighterCommand { private: Fighter* fighter; int direction; public: MoveCommand(Fighter* fighter, int direction) { this->fighter = fighter; this->direction = direction; } void execute() { fighter->move(direction); } }; //***********************************************Invoker*********************************** //請求者 class Controller { private: FighterCommand* cmdMissible; FighterCommand* cmdBomb; FighterCommand* cmdMoveLeft; FighterCommand* cmdMoveRight; public: Controller(FighterCommand* missible, FighterCommand* bomb, FighterCommand* left, FighterCommand* right) { cmdMissible = missible; cmdBomb = bomb; cmdMoveLeft = left; cmdMoveRight = right; } void missible() { cmdMissible->execute(); } void bomb() { cmdBomb->execute(); } void moveLeft() { cmdMoveLeft->execute(); } void moveRight() { cmdMoveRight->execute(); } }; int main() { Fighter fighter; //戰士(命令接收者) //命令物件 FighterCommand* cmdMissible = new MissileCommand(&fighter); FighterCommand* cmdBomb = new BombCommand(&fighter); FighterCommand* cmdMoveLeft = new MoveCommand(&fighter, 3); FighterCommand* cmdMoveRight = new MoveCommand(&fighter, 4); //玩家(命令發出者) //引數化:將命令物件作為引數傳入Invoker Controller player(cmdMissible, cmdBomb, cmdMoveLeft, cmdMoveRight); player.bomb(); player.missible(); player.moveLeft(); player.moveRight(); //為什麼不直接fighter.bomb()之類的呢? //有時當發出命令後,我們只關心任務是否完成?但任務具體由哪個Fighter執行 //並不是我們關心的.如果直接呼叫,意味著命令的發出者直接叫某個具體的Fighter去做 //這樣兩者的耦合太緊.利用命令模式可以將兩者解耦。 return 0; }
(3)思考命令模式
①命令模式的本質:封裝請求。這是也命令模式的關鍵。把請求封裝成物件,也就是命令物件,並定義了統一的執行操作的介面。這個命令物件可以被儲存、轉發、記錄、處理、撤銷等。整個命令模式都是圍繞這個物件進行的。
②命令模式的動機:在軟體構建過程中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合——比如需要對行為進行“記錄、撤銷/重做”、事務”等處理,這種無法抵禦變化的緊耦合是不合適的,命令模式的動機就是將一組行為抽象為物件,可以實現二者之間的構耦合。
③命令模式的組裝和呼叫
命令的組裝者:用它維護命令的“虛”實現和真實實現之間的關係
④命令的接收者:可以是任意的類,對它沒有特殊要求。一個接收者可以處理多個命令,接收者提供的方法個數、名稱、功能和命令物件中的可以不一樣,只要能夠通過呼叫接收者的方法來實現命令對應的功能就可以了。
⑤智慧命令:在標準的命令模式中,命令的實現類是沒有真正實現命令要求的功能的,真正執行命令的功能的是接收者。如果命令物件自己實現了命令要求的功能,而不再需要呼叫接收者,那這種情況稱為智慧命令。也有半智慧的命令,即命令物件實現一部分,其他的還是需要呼叫接收者來完成,也就是說命令的功能由命令物件和接收者共同來完成。
【程式設計實驗】選單項命令
//行為型模式:命令模式
//場景:選單項命令
/*
要求:某軟體公司欲開發一個基於Windows平臺的公告板系統。系統提供一個主選單(Menu),
在主選單中包含了一些選單項(MenuItem),可以通過Menu類的addMenuItem()方法增加
選單項。選單項的主要方法是click(),每一個選單項包含一個抽象命令類,具體命令
類包括OpenCommand(開啟命令),CreateCommand(新建命令),EditCommand(編輯命令)
等,命令類具有一個execute()方法,用於呼叫公告板系統介面類(Board)的open()、
create()、edit()等方法。試使用命令模式設計該系統,使得MenuItem類與Board類
的耦合度降低,繪製類圖並程式設計模擬實現。
*/
#include <iostream>
#include <string>
#include <map>
using namespace std;
//******************************Receiver********************
//Board(公告板)
class Board
{
public:
void open()
{
cout <<"Board opened!" << endl;
}
void create()
{
cout << "Board create!" << endl;
}
void edit()
{
cout << "Board edit!" << endl;
}
};
//****************************Command************************
//Command命令介面
class Command
{
public:
virtual void execute() = 0;
};
//CreateCommand(“新建”)
class CreateCommand : public Command
{
private:
Board* board;
public:
CreateCommand(Board* board)
{
this->board = board;
}
void execute()
{
board->create();
}
};
//OpenCommand(“開啟”)
class OpenCommand : public Command
{
private:
Board* board;
public:
OpenCommand(Board* board)
{
this->board = board;
}
void execute()
{
board->open();
}
};
//EditCommand(“編輯”)
class EditCommand : public Command
{
private:
Board* board;
public:
EditCommand(Board* board)
{
this->board = board;
}
void execute()
{
board->edit();
}
};
//*****************************Invoker****************************
//MenuItem(選單項)
class MenuItem
{
private:
string itemName;
Command* command;
public:
MenuItem(string name,Command* command)
{
this->command = command;
this->itemName = name;
}
//單擊事件
void click()
{
//因選單項(命令傳送者)與具體的訊息接收者(如board)的解耦
//因此,對於不同的選單項,這裡都可以像如下那樣處理。(簡潔、靈活!)
command->execute();
}
string& getItemName()
{
return itemName;
}
void setItemName(string name)
{
this->itemName = name;
}
Command* getCommand()
{
return command;
}
void setCommand(Command* newCommand)
{
this->command = newCommand;
}
};
//Menu(選單)
class Menu
{
private:
map<string,MenuItem*> items;
public:
void addMenuItem(MenuItem* item)
{
items[item->getItemName()] = item;
}
MenuItem* getMenuItemByName(string name)
{
return items[name];
}
};
int main()
{
//建立公告板(命令接收者)
Board board;
//建立3個命令物件並與board組裝起來
CreateCommand createCmd(&board);
OpenCommand openCmd(&board);
EditCommand editCmd(&board);
//建立主選單及選單項
Menu menu;
MenuItem* open = new MenuItem("open",&openCmd);
MenuItem* create = new MenuItem("create", &createCmd);
MenuItem* edit = new MenuItem("edit", &editCmd);
menu.addMenuItem(open);
menu.addMenuItem(create);
menu.addMenuItem(edit);
//測試
//單擊“建立公告板”選單項
menu.getMenuItemByName("create")->click();
//單擊“開啟公告板”選單項
menu.getMenuItemByName("open")->click();
//單擊“編輯公告板”選單項
menu.getMenuItemByName("edit")->click();
delete open;
delete create;
delete edit;
return 0;
}
2. 深度理解命令模式
(1)引數化配置:用不同的命令物件,去引數化配置客戶的請求。
(2)可撤銷的操作:
①補償式(反操作式):如被撤銷的操作是加的功能,那麼反操作就是減的功能。
②儲存恢復式:把操作前的狀態記錄下來,然後要撤銷操作時就直接恢復回去就可以了。(該種方式會放到備忘錄模式中進行講解)
【程式設計實驗】可撤銷/恢復操作的計算器
//行為型模式:命令模式
//場景:計算器(可撤銷的計算)
#include <iostream>
#include <string>
#include <list>
using namespace std;
//***************************************Receiver*******************
//操作運算的介面
class OperationApi
{
public:
//獲取計算完成後的結果
virtual int getResult() = 0;
//設計開始計算的初始值
virtual void setResult(int result) = 0;
//加法
virtual void add(int num)=0;
//減法
virtual void substract(int num) = 0;
};
//運算類,真正實現加減法運算(具體的接收者)
class Operation : public OperationApi
{
private:
int result;
public:
int getResult()
{
return result;
}
void setResult(int result)
{
this->result = result;
}
void add(int num)
{
result +=num;
}
void substract(int num)
{
result -= num;
}
};
//*************************Command***********************
//命令介面,支援可撤銷操作
class Command
{
public:
//執行命令的操作
virtual void execute() = 0;
//執行撤銷的操作
virtual void undo() = 0;
};
//具體的加法命令
class AddCommand : public Command
{
private:
//持有具體執行計算的物件(命令的接收者)
OperationApi* operation;
//要加上的資料
int opeNum;
public:
AddCommand(OperationApi* operation, int opeNum)
{
this->opeNum = opeNum;
this->operation = operation;
}
void execute()
{
operation->add(opeNum);
}
void undo()
{
operation->substract(opeNum);
}
};
//具體的減法命令
class SubstractCommand : public Command
{
private:
//持有具體執行計算的物件(命令的接收者)
OperationApi* operation;
//要減去的資料
int opeNum;
public:
SubstractCommand(OperationApi* operation, int opeNum)
{
this->opeNum = opeNum;
this->operation = operation;
}
void execute()
{
operation->substract(opeNum);
}
void undo()
{
operation->add(opeNum);
}
};
//*****************************Invoker************************************
//計算器類,計算器上有加法按鈕和減法按鈕
class Calculator
{
private:
Command* addCmd; //加法命令物件
Command* substractCmd; //減法命令物件
//命令的操作歷史記錄,在撤銷的時候用
list<Command*> undoCmds;
//命令被撤銷的歷史記錄,在恢復時使用
list<Command*> redoCmds;
public:
void setAddCmd(Command* addCmd)
{
this->addCmd = addCmd;
}
void setSubstractCmd(Command* subCmd)
{
this ->substractCmd = subCmd;
}
//提供給客戶使用,執行加法功能
void addPressed()
{
addCmd->execute();
//把操作記錄到歷史記錄裡面
undoCmds.push_back(addCmd);
}
//提供給客戶使用,執行減法功能
void substractPressed()
{
substractCmd->execute();
//把操作記錄到歷史記錄裡面
undoCmds.push_back(substractCmd);
}
void undoPressed()
{
if(undoCmds.size() > 0)
{
//取出最後一個命令來撤銷
Command* cmd = undoCmds.back();
cmd->undo();
//如果還有恢復功能,那就把這個命令記錄到恢復歷史列表中
redoCmds.push_back(cmd);
//然後把最後一個命令刪除掉
undoCmds.pop_back();
}
else
{
cout << "Sorry, nothing to undo()" << endl;
}
}
void redoPressed()
{
if(redoCmds.size() > 0)
{
//取出最後一個命令來重做
Command* cmd = redoCmds.back();
cmd->execute();
//把這個命令記錄到可撤銷的歷史記錄裡面
undoCmds.push_back(cmd);
//然後把最後一個命令刪除掉
redoCmds.pop_back();
}
else
{
cout << "Sorry, nothing to redo()" << endl;
}
}
};
int main()
{
//客戶端
//1.組裝命令和接收者
//建立接收者
OperationApi* operation = new Operation();
//建立命令物件,並組裝命令和接收者
AddCommand addCmd(operation, 5);
SubstractCommand subCmd(operation, 3);
//2.把命令設定到持有者,也就是計算器裡面
Calculator calculator;
calculator.setAddCmd(&addCmd);
calculator.setSubstractCmd(&subCmd);
//3. 模擬按下按鈕,測試一下
calculator.addPressed();
cout << "一次加法運算後的結果為:" << operation->getResult() << endl;
calculator.substractPressed();
cout << "一次減法運算後的結果為:" << operation->getResult() << endl;
//測試撤銷
calculator.undoPressed();
cout << "撤銷一次後的結果為:" << operation->getResult() << endl;
calculator.undoPressed();
cout << "再次撤銷一次後的結果為:" << operation->getResult() << endl;
//測試恢復
calculator.redoPressed();
cout << "恢復操作一次後的結果為:" << operation->getResult() << endl;
calculator.redoPressed();
cout << "再次恢復操作一次後的結果為:" << operation->getResult() << endl;
return 0;
}
(3)巨集命令
①巨集命令(Macro Command)又稱為組合命令,它是組合模式和命令模式聯用的產物。巨集命令是一個具體命令類,它擁有一個集合屬性,在該集合中包含了對其他命令物件的引用。
②通常巨集命令不直接與請求接收者互動,而是通過它的成員來呼叫接收者的方法。當呼叫巨集命令的execute()方法時,將遞迴呼叫它所包含的每個成員命令的execute()方法,一個巨集命令的成員可以是簡單命令,還可以繼續是巨集命令。
③執行一個巨集命令將觸發多個具體命令的執行,從而實現對命令的批處理
【程式設計實驗】餐館點菜
//行為型模式:命令模式
//場景:餐館點菜
//角色:
//1.接收者:廚師,是命令的真正執行者。本例分為兩種:做熱菜的廚師和做冷盤的廚師
//2.服務員:負責命令和接收者的組裝,並持有命令物件(選單)
// 最後啟動命令的也是服務員
//3.命令物件:A、每一道菜是個命令物件;B、選單(組合物件,由多道菜組成)
#include <iostream>
#include <string>
#include <list>
#include <typeinfo>
using namespace std;
//***************************************Receiver*******************
//接收者:兩種:做熱菜的廚師和做冷盤的廚師
//廚師的介面
class CookApi
{
public:
//做菜的方法
//引數:菜名
virtual void cook(string name) = 0;
};
//做熱菜的廚師
class HotCook : public CookApi
{
public:
void cook(string name)
{
cout << "本廚師正在做: " << name <<"(熱菜)" << endl;
}
};
//做冷盤的廚師
class CoolCook : public CookApi
{
public:
void cook(string name)
{
cout << "冷盤" << name << "己經做好了,本廚師正在裝盤。" << endl;
}
};
//************************************Command**************************
//命令介面,宣告執行的操作
class Command
{
public:
virtual void execute() = 0;
};
//具體的命令物件(三道菜):
//兩道熱菜:北京烤鴨、綠豆排骨煲
//一道冷盤:蒜泥白肉
//1.命令物件:北京烤鴨
class DuckCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
public:
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
void execute()
{
cookApi->cook("北京烤鴨");
}
};
//2. 命令物件:綠豆排骨煲
class ChopCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
public:
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
void execute()
{
cookApi->cook("綠豆排骨煲");
}
};
//3.命令物件:蒜泥白肉
class PorkCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
public:
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
void execute()
{
cookApi->cook("蒜泥白肉");
}
};
//4. 選單物件,是個巨集命令物件
//A、本質上還是一個命令,要繼承自Command
//B、與普通命令不同,巨集命令是多個命令的組合
//C、執行巨集命令相當於依次執行巨集命令中包含的多個命令物件
class MenuCommand : public Command
{
private:
//用來記錄組合本選單的多道菜品,也就是多個命令物件
list<Command*> lstCmds;
public:
//點菜,把菜品加入到選單中
//引數為客戶點的菜
void addCommand(Command* cmd)
{
lstCmds.push_back(cmd);
}
//執行選單,其實就是迴圈執行選單裡面的每個菜
void execute()
{
list<Command*>::iterator iter = lstCmds.begin();
while( iter != lstCmds.end())
{
(*iter)->execute();
++iter;
}
}
};
//**************************************Invoker + Client*******************
//服務員
class Waiter
{
private:
//持有一個巨集命令物件(選單)
MenuCommand menuCmd;
//
HotCook hotCook;
CoolCook coolCook;
public:
//客戶點菜
//引數客戶點的菜,每道菜是一個命令物件
void orderDish(Command* cmd)
{
//客戶傳過來的命令物件
//在這裡進行組裝
//判斷到底是熱菜還是冷盤
if(typeid(*cmd) == typeid(DuckCommand))
{
//交給熱菜師傅
((DuckCommand*)cmd)->setCookApi(&hotCook);
}
else if(typeid(*cmd) == typeid(ChopCommand))
{
//交給熱菜師傅
((ChopCommand*)cmd)->setCookApi(&hotCook);
}
else if(typeid(*cmd) == typeid(PorkCommand))
{
//交給冷盤師傅
((PorkCommand*)cmd)->setCookApi(&coolCook);
}
menuCmd.addCommand(cmd);
}
//客戶點菜完畢,表示要執行命令了,這裡就是執行選單
//這個組合命令了
void orderOver()
{
menuCmd.execute();
}
};
int main()
{
//客戶端
//客戶只負責向服務員點菜就好了
//建立服務員
Waiter waiter;
//建立命令物件,就是要點的菜
Command* chop = new ChopCommand();
Command* duck = new DuckCommand();
Command* pork = new PorkCommand();
//點菜,就是服務員記錄下來
waiter.orderDish(chop);
waiter.orderDish(duck);
waiter.orderDish(pork);
//點菜完畢
waiter.orderOver();
delete chop;
delete duck;
delete pork;
return 0;
}
/*輸出結果:
本廚師正在做: 綠豆排骨煲(熱菜)
本廚師正在做: 北京烤鴨(熱菜)
冷盤蒜泥白肉己經做好了,本廚師正在裝盤。
*/
(4)佇列請求
//行為型模式:命令模式
//場景:餐館點菜(佇列)
//角色:
//1.接收者:廚師,是命令的真正執行者。
//2.服務員:負責命令和接收者的組裝,並持有命令物件(選單)
// 最後啟動命令的也是服務員
//3.命令物件:A、每一道菜是個命令物件;B、選單(組合物件,由多道菜組成)
// * 編譯命令:
//* g++ main.cpp -std=c++11
#include <iostream>
#include <string>
#include <list>
#include <thread> //C++ 11
#include <mutex>
using namespace std;
//同步物件
static mutex g_mutex; //對佇列進行同步
static mutex g_PrintMutex;//對控制檯輸出同步
class CookApi; //前向宣告
//************************************Command**************************
//命令介面,宣告執行的操作
class Command
{
public:
//執行命令對應的操作
virtual void execute() = 0;
//設定命令的接收者
virtual void setCookApi(CookApi* cooker) = 0;
//獲取點菜的桌號
virtual int getTableNum() = 0;
};
//命令佇列類
class CommandQueue
{
public:
//用來儲存命令物件的佇列
static list<Command*> cmds;
public:
//服務員傳過來一個新的選單,需要同步
//同時,會有很多服務員傳入選單,而同時又有很多廚師從這個
//佇列中取值
static void addMenu(list<Command*>& menu)
{
g_mutex.lock();
list<Command*>::iterator iter = menu.begin();
while (iter != menu.end())
{
cmds.push_back((*iter));
++iter;
}
g_mutex.unlock();
}
//廚師從命令佇列裡面獲取命令物件進行處理(需要同步)
static Command* getOneCommand()
{
Command* ret = NULL;
g_mutex.lock();
if (cmds.size() > 0)
{
//取出佇列的第一個,因為是約定的按照加入的先後來處理
ret = cmds.front();
//同時從佇列裡面取掉這個命令物件
cmds.pop_front();
}
g_mutex.unlock();
return ret;
}
};
list<Command*> CommandQueue::cmds;
//***************************************Receiver*******************
//接收者:兩種:做熱菜的廚師和做冷盤的廚師
//廚師的介面
class CookApi
{
public:
//做菜的方法
//引數:tableName-點菜的桌名,name-菜名
virtual void cook(int tableNum, string name) = 0;
};
//做熱菜的廚師
class HotCook : public CookApi
{
private:
string name; //廚師的名字
public:
HotCook(string name)
{
this->name = name;
}
void cook(int tableNum, string name)
{
g_PrintMutex.lock();
cout << this->name << "廚師正在為" <<tableNum
<<"號桌做:"<< name << endl;
int cookTime = rand() % 5;
this_thread::sleep_for(chrono::seconds(cookTime));
cout << this->name << "廚師為"<< tableNum << "號桌做好了:"
<< name << ",共計耗時=" << cookTime << "秒" <<endl;
g_PrintMutex.unlock();
}
void startWork()
{
while (true)
{
//不斷從命令佇列裡面獲取命令物件
Command* cmd = CommandQueue::getOneCommand();
if (cmd != NULL)
{
//說明取到命令物件了,這個命令物件還沒有設定接收者
//因為前面都還不知道到底哪一個廚師來真正執行這個命令
//現在知道了,就是當前廚師例項,設定到命令物件裡面
cmd->setCookApi(this);
//然後真正執行這個命令
cmd->execute();
}
//體息一下
this_thread::sleep_for(chrono::milliseconds(1000));
}
}
};
//****************************************具體命令物件*******************************************************
//具體的命令物件(三道菜):北京烤鴨、綠豆排骨煲、蒜泥白肉
//1.命令物件:北京烤鴨
class DuckCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
//點菜的桌號
int tableNum;
public:
DuckCommand(int tableNum)
{
this -> tableNum = tableNum;
}
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
int getTableNum(){return tableNum;}
void execute()
{
cookApi->cook(tableNum, "北京烤鴨");
}
};
//2. 命令物件:綠豆排骨煲
class ChopCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
//點菜的桌號
int tableNum;
public:
ChopCommand(int tableNum)
{
this -> tableNum = tableNum;
}
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
int getTableNum(){return tableNum;}
void execute()
{
cookApi->cook(tableNum, "綠豆排骨煲");
}
};
//3.命令物件:蒜泥白肉
class PorkCommand: public Command
{
private:
//持有具體做菜的廚師的物件
CookApi* cookApi;
//點菜的桌號
int tableNum;
public:
PorkCommand(int tableNum)
{
this -> tableNum = tableNum;
}
void setCookApi(CookApi* cookApi)
{
this->cookApi = cookApi;
}
int getTableNum(){return tableNum;}
void execute()
{
cookApi->cook(tableNum, "蒜泥白肉");
}
};
//4. 選單物件,是個巨集命令物件
//A、本質上還是一個命令,要繼承自Command
//B、與普通命令不同,巨集命令是多個命令的組合
//C、執行巨集命令相當於依次執行巨集命令中包含的多個命令物件
class MenuCommand : public Command
{
private:
//用來記錄組合本選單的多道菜品,也就是多個命令物件
list<Command*> lstCmds;
public:
//點菜,把菜品加入到選單中
//引數為客戶點的菜
void addCommand(Command* cmd)
{
lstCmds.push_back(cmd);
}
void setCookApi(CookApi* cooker)
{
//什麼都不做,這個方法對組合命令物件的選單沒有意義
}
int getTableNum()
{
//什麼都不做,這個方法對組合命令物件的選單沒有意義
return 0;
}
//執行選單
void execute()
{
//這裡與之前的例子不同,把選單傳給佇列
CommandQueue::addMenu(lstCmds);
}
list<Command*>& getCommands()
{
return lstCmds;
}
};
//**************************************Invoker + Client*******************
//服務員
class Waiter
{
private:
//持有一個巨集命令物件(選單)
MenuCommand menuCmd;
public:
//客戶點菜
//引數客戶點的菜,每道菜是一個命令物件
void orderDish(Command* cmd)
{
menuCmd.addCommand(cmd);
}
//客戶點菜完畢,表示要執行命令了,這裡就是執行選單
//這個組合命令了
void orderOver()
{
menuCmd.execute();
}
};
//執行緒函式
void thread_entry(HotCook* cooker)
{
cooker->startWork();
}
//客戶端
int main()
{
srand( time(NULL) );
//建立三位廚師
HotCook cook1("張三");
HotCook cook2("李四");
HotCook cook3("王五");
//啟動3個工作執行緒
thread t1(thread_entry,&cook1);
thread t2(thread_entry,&cook2);
thread t3(thread_entry,&cook3);
//為了簡單,直接用迴圈模擬多個桌號點菜
//建立服務員
for(int i = 0; i < 5; i++)
{
Waiter* waiter = new Waiter();
//建立命令物件,就是要點的菜
Command* chop = new ChopCommand(i);
Command* duck = new DuckCommand(i);
Command* pork = new PorkCommand(i);
//點菜,就是服務員記錄下來
waiter->orderDish(chop);
waiter->orderDish(duck);
waiter->orderDish(pork);
//點菜完畢
waiter->orderOver();
}
//為簡化,這裡省略了對Waiter、Commands物件的釋放。。。
t1.join();
t2.join();
t3.join();
return 0;
}
(5)日誌請求
①概念:把請求的歷史記錄儲存下來,一般採用永久儲存方式。如果在執行請求中,系統崩潰了,那麼當再次執行時,就可以從儲存的歷史記錄中獲取日誌請求,並重新執行命令。
②實現方法:在命令物件中新增上儲存和裝載的方法,其實就是讓命令物件自己實現類似序列化的功能。
3. 命令模式的優缺點
(1)優點:
①更鬆散的耦合:命令的發起者和具體實現命令的物件(接收者完全解耦)。也就是說傳送者完全不知道具體實現物件是誰。
②更動態的控制:把請求封裝起來,可以動態地對它進行引數化、佇列化和日誌化。
③很自然的複合命令:命令模式能夠很容易地組合成複合命令,也就是巨集命令,從而使系統操作更簡單,功能更強大。
④更好的擴充套件性:擴充套件命令很容易,只需要實現新的命令物件,然後裝配就可以使用,己有的實現完全不用變化。
(2)缺點: 如果有N個命令,則Command的子類就需要N個,這會造成子類的膨脹。
4. 應用場景
(1)需要抽象出執行的動作,並引數化這些物件時可選用命令模式
(2)需要不同的時刻指定、排列和執行請求,可將請求封裝成為命令物件,然後實現將請求佇列化。
(3)需要支援取消操作,可通過管理命令物件,能很容易實現命令的恢復和重做功能。
(4)在需要事務的系統中,可選用命令模式,它提供了對事務進行建模的方法。
5. 相關模式
(1)命令模式和組合模式
命令模式中,巨集命令可使用組合模式.(但注意,之前的例子沒有使用組合模式)
(2)命令模式與備忘錄模式
①命令模式中,實現可撤銷功能時,有兩種實現方法,其中一種是儲存命令執行前的狀態,撤銷時把狀態恢復。也可以考慮使用備忘錄模式
②如果狀態儲存在命令物件中,也可以使用原型模式,把命令物件當作原型來克隆一個新的物件,然後將克隆出來的物件通過備忘錄模式存放。