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)思考命令模式
①命令模式的本质:封装请求。这是也命令模式的关键。把请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口。这个命令对象可以被存储、转发、记录、处理、撤销等。整个命令模式都是围绕这个对象进行的。
②命令模式的动机:在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重做”、事务”等处理,这种无法抵御变化的紧耦合是不合适的,命令模式的动机就是将一组行为抽象为对象,可以实现二者之间的构耦合。
③命令模式的组装和调用
命令的组装者:用它维护命令的“虚”实现和真实实现之间的关系。(即下图中的Client,但将之定义成组装者更合适。真正的Client是通过Invoker来触发命令的)
④命令的接收者:可以是任意的类,对它没有特殊要求。一个接收者可以处理多个命令,接收者提供的方法个数、名称、功能和命令对象中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了。
⑤智能命令:在标准的命令模式中,命令的实现类是没有真正实现命令要求的功能的,真正执行命令的功能的是接收者。如果命令对象自己实现了命令要求的功能,而不再需要调用接收者,那这种情况称为智能命令。也有半智能的命令,即命令对象实现一部分,其他的还是需要调用接收者来完成,也就是说命令的功能由命令对象和接收者共同来完成。
【编程实验】菜单项命令
//行为型模式:命令模式 //场景:菜单项命令 /* 要求:某软件公司欲开发一个基于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)命令模式与备忘录模式
①命令模式中,实现可撤销功能时,有两种实现方法,其中一种是保存命令执行前的状态,撤销时把状态恢复。也可以考虑使用备忘录模式
②如果状态存储在命令对象中,也可以使用原型模式,把命令对象当作原型来克隆一个新的对象,然后将克隆出来的对象通过备忘录模式存放。