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; }