C++设计模式之命令模式

来源:互联网 发布:黑人歧视黄种人知乎 编辑:程序博客网 时间:2024/05/17 03:09

概述:

        我们去餐厅吃饭,我们是通过服务员来点菜,具体是谁来做这些菜和他们什么时候完成的这些菜,其实我们都不知道。抽象之,“菜单请求者”我们和“菜单实现者”厨师,2者之间是松耦合的,我们对这些菜的其他一些请求比如“撤销,重做”等,我们也不知道是谁在做。其实这就是本文要说的Command模式。

        将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。[GOF 《设计模式》]

类图与实例:


角色用途:

客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。

命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。

具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执行方法。

请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。

接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

实例:

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <vector>  
  3. using namespace std;  
  4.   
  5.   
  6. // 烤肉师傅  
  7. class RoastCook  
  8. {  
  9. public:  
  10.     void MakeMutton() { cout << "烤羊肉" << endl; }  
  11.     void MakeChickenWing() { cout << "烤鸡翅膀" << endl; }  
  12. };  
  13.   
  14.   
  15. // 抽象命令类  
  16. class Command  
  17. {  
  18. public:  
  19.     Command(RoastCook* temp) { receiver = temp; }  
  20.     virtual void ExecuteCmd() = 0;  
  21.   
  22. protected:  
  23.     RoastCook* receiver;  
  24. };  
  25.   
  26. // 烤羊肉命令  
  27. class MakeMuttonCmd : public Command  
  28. {  
  29. public:  
  30.     MakeMuttonCmd(RoastCook* temp) : Command(temp) {}  
  31.     virtual void ExecuteCmd() { receiver->MakeMutton(); }  
  32. };  
  33.   
  34. // 烤鸡翅膀命令  
  35.   
  36. class MakeChickenWingCmd : public Command  
  37. {  
  38. public:  
  39.     MakeChickenWingCmd(RoastCook* temp) : Command(temp) {}  
  40.     virtual void ExecuteCmd() { receiver->MakeChickenWing(); }  
  41. };  
  42.   
  43. // 服务员类  
  44. class Waiter  
  45. {  
  46. public:  
  47.     void SetCmd(Command* temp);  
  48.   
  49.     // 通知执行  
  50.     void Notify();  
  51. protected:  
  52.     vector<Command*> m_commandList;  
  53. };  
  54.   
  55. void Waiter::SetCmd(Command* temp)  
  56. {  
  57.     m_commandList.push_back(temp);  
  58.     cout << "增加订单" << endl;  
  59. }  
  60.   
  61. void Waiter::Notify()  
  62. {  
  63.     vector<Command*>::iterator it;  
  64.     for (it=m_commandList.begin(); it!=m_commandList.end(); ++it)  
  65.     {  
  66.         (*it)->ExecuteCmd();  
  67.     }  
  68. }  
  69.   
  70. int main()  
  71. {  
  72.     // 店里添加烤肉师傅、菜单、服务员等顾客  
  73.     RoastCook* cook = new RoastCook();  
  74.     Command* cmd1 = new MakeMuttonCmd(cook);  
  75.     Command* cmd2 = new MakeChickenWingCmd(cook);  
  76.     Waiter* girl = new Waiter();  
  77.   
  78.     // 点菜  
  79.     girl->SetCmd(cmd1);  
  80.     girl->SetCmd(cmd2);  
  81.   
  82.     // 服务员通知  
  83.     girl->Notify();  
  84.     return 0;  
  85. }  

效果与实现要点:

1Command模式的根本目的在于将行为请求者行为实现者解耦,在面向对象语言中,常见的实现手段是将行为抽象为对象

2.实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。

3.通过使用Compmosite模式,可以将多个命令封装为一个复合命令”MacroCommand

4Command模式与C#中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的接口-实现来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱。

5.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

适用性:

在下面的情况下应当考虑使用命令模式:

1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。

3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。


========================================================

前言

又要过年了,又是一个抢票季;从大学起,到现在工作,一直都是在外地,离家千里;以前买票,曾经也去火车站通宵排队买票;直到12306的腾空出现,在电脑前不停止的点着鼠标刷票,那个时候12306很是脆弱,抢一张票更是难上加难;现在好了,慢慢强大的12306,买票时出现了一个排队系统,先买票,进入12306的排队系统;然后,系统一个一个的处理大家的请求,一旦你的购票请求进入了排队系统,你就无法再次进行刷票了,除非你退出排队系统;这就减少了购票者的刷票次数;减少了12306后台服务器的处理压力。那么,你有没有想过,12306是如何将你的购票请求加入排队系统的呢?这样的排队系统是如何实现的呢?而我今天总结的命令模式,将会对此进行简单的剖析。

什么是命令模式?

在GOF的《设计模式:可复用面向对象软件的基础》一书中对命令模式是这样说的:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。在OOP中,一切都是对象,将请求封装成对象,符合OOP的设计思想,当将客户的单个请求封装成对象以后,我们就可以对这个请求存储更多的信息,使请求拥有更多的能力;命令模式同样能够把请求发送者和接收者解耦,使得命令发送者不用去关心请求将以何种方式被处理。

我们在12306上,单击购票,这是一个请求,12306将这个请求封装为一个对象,在12306还没有上线排队系统时,你买票是这样的:你不停的用鼠标点击12306网站上的购票按钮,直到你买到了票;对于你的每一次点击,服务器都要进行处理,做出响应,告诉你,有没有买到票;这样,可能就会出现很多次无效的点击,但是这些无效的点击却增加了服务器的负担。增加了排队系统以后,你的购票请求就进入了对应的购票队列,一旦你进入了购票队列,当你再次鼠标单击购票时,12306会拒绝你的购票请求,它会告诉你,你已经进入了购票队列;处于购票队列中的你,你可以选择退出购票队列去购买其它车次的车票,从而进入其它购票队列。这样就有效的减少了购票者发送很多无效的购票请求。

这就好比票是共享资源,谁都想要,但是票的数量是一定的;在没有排队系统之前,大家的购票请求都是去竞争这个票,服务器对于大家对于共享资源——票的竞争进行互斥,谁抢到了,票就少一张;而现在有了购票队列以后,大家都不用去竞争了,按时间的先后顺序排好队,12306把票一张张的发给进入队列的购票者。

UML类图

果冻想 | 一个原创文章分享网站
Command:声明执行操作的接口;
ConcreteCommand:将一个接收者对象绑定于一个动作,之后,调用接收者相应的操作,以实现Execute来完成相应的命令;
Client:创建一个具体命令对象,但是并没有设定它的接收者;
Invoker:要求该命令执行这个请求;
Receiver:知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。

以上这些对象是按照下面的方式进行协作的:

  1. Client创建一个ConcreteCommand命令对象,并指定它的Receiver对象;
  2. Invoker对象存储该ConcreteCommand对象;
  3. 该Invoker通过调用Command对象的Execute操作来提交一个请求。如果这个命令请求是可以撤销的,ConcreteCommand就执行Execute操作之前存储当前状态以用于取消该命令请求;
  4. ConcreteCommand对象调用Receiver的一些操作以执行该请求。

使用场合

使用命令模式实现12306(工程下载):
CHomePage类,表示12306的官网订票页面;
C12306Processor类,是后台真正处理用户的请求的类,专门进行出票;
Command类,表示用户的购票命令请求;
Customer类,表示购票的用户。
由于代码较多,这里只提供工程的下载。

这里再提供命令模式的一般实现:

#include <iostream>using namespace std;#define SAFE_DELETE(p) if (p) { delete p; p = NULL; }class Receiver{public:     void Action()     {          cout<<"Receiver->Action"<<endl;     }};class Command{public:     virtual void Execute() = 0;};class ConcreteCommand : public Command{public:     ConcreteCommand(Receiver *pReceiver) : m_pReceiver(pReceiver){}     void Execute()     {          m_pReceiver->Action();     }private:     Receiver *m_pReceiver;};class Invoker{public:     Invoker(Command *pCommand) : m_pCommand(pCommand){}     void Invoke()     {          m_pCommand->Execute();     }private:     Command *m_pCommand;};int main(){     Receiver *pReceiver = new Receiver();     Command *pCommand = new ConcreteCommand(pReceiver);     Invoker *pInvoker = new Invoker(pCommand);     pInvoker->Invoke();     SAFE_DELETE(pInvoker);     SAFE_DELETE(pCommand);     SAFE_DELETE(pReceiver);     return 0;}

总结

命令模式是一个很经典的模式,我的理解也不会很到位;在我们的身边,就存在很多的使用命令模式的例子,数据库中的事务就是使用命令模式去实现的,在C#中的委托也是使用命令模式去实现的。我在这里只是将我在学习过程中理解到的东西记录了下来和大家分享。可能有的地方我的理解也存在差错,希望大家和我分享你对命令模式的理解。

2014年1月17日 于大连,东软。

============================================================================

Command命令模式
作用:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

由于“行为请求者”与“行为实现者”的紧耦合,使用命令模式,可以对请求排队或记录请求日志,以及支持可撤销的操作。

UML图:


Command类,用来声明执行操作的接口

ConcreteCommand,将一个接收者对象绑定于一个操作,调用接收者相应的操作,以实现Execute

Invoker类,要求该命令执行这个请求

Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者。

Command模式通过将请求封装到一个对象Command中,并将请求的接收者存放到具体的ConcreteCommand类中,从而实现调用操作的对象和操作的具体实现者之间的解耦。

Command模式结构图中,将请求的接收者(处理者)放到Command的具体子类ConcreteCommand中,当请求到来时(Invoker发出Invoke消息激活Command对象),ConcreteCommand将处理请求交给Receiver对象进行处理。

命令模式的优点:
1,它能较容易地设计一个命令队列;
2,在需要的情况下,可以较容易地将命令记入日志;
3,允许接收请求的一方决定是否要否决请求。
4,可以容易地实现对请求的撤销和重做;
5,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

Command模式关键就是讲一个请求封装到一个类中(Command),再提供处理对象(Receiver),最后Command命令由Invoker激活。另外,我们可以将请求接收者的处理抽象出来作为参数传给Command对象,实际也就是回调的机制来实现这一点。也就是讲处理操作方法地址通过参数传递给Command对象,Command对象在适当的时候再调用该函数。

Command模式将调用操作的对象和知道如何实现该操作的对象解耦,在上面Command的结构图中,Invoker对象根本就不知道具体的是哪个对象在处理Execute操作(当然要知道是Command类别的对象)。
在Command要增加新的处理操作对象很容易,我们可以通过创建新的继承自Command的子类来实现这一点。
Command模式可以和Memento模式结合起来,支持取消的操作。

代码如下:

Command.h

复制代码
 1 #ifndef _COMMAND_H_ 2 #define _COMMAND_H_ 3  4 class Command 5 { 6 public: 7     virtual ~Command(); 8     virtual void Execute()=0; 9 protected:10     Command();11 private:12 };13 14 class Receiver;15 16 class ConcreteCommand : public Command17 {18 public:19     ConcreteCommand(Receiver* pReceiver);20     ~ConcreteCommand();21     virtual void Execute();22 protected:23 private:24     Receiver* _recv;25 };26 27 class Invoker28 {29 public:30     Invoker(Command* pCommand);31     ~Invoker();32     void Invoke();33 protected:34 private:35     Command* _cmd;36 };37 38 class Receiver39 {40 public:41     Receiver();42     ~Receiver();43     void Action();44 protected:45 private:46 };47 #endif
复制代码

Command.cpp

复制代码
 1 #include "Command.h" 2 #include <iostream> 3  4 using namespace std; 5  6 Command::Command() 7 {} 8  9 Command::~Command()10 {}11 12 ConcreteCommand::ConcreteCommand(Receiver* pReceiver)13 {14     this->_recv = pReceiver;15 }16 17 ConcreteCommand::~ConcreteCommand()18 {}19 20 void ConcreteCommand::Execute()21 {22     this->_recv->Action();23 }24 25 Receiver::Receiver()26 {}27 28 Receiver::~Receiver()29 {}30 31 void Receiver::Action()32 {33     cout << "Receiver::Action" << endl;34 }35 36 Invoker::Invoker(Command* pCommand)37 {38     this->_cmd = pCommand;39 }40 41 Invoker::~Invoker()42 {}43 44 void Invoker::Invoke()45 {46     this->_cmd->Execute();47 }
复制代码

main.cpp

复制代码
 1 #include "Command.h" 2  3 int main() 4 { 5     //创建具体命令对象pCmd并设定它的接收者pRev 6     Receiver* pRev = new Receiver(); 7     Command* pCmd = new ConcreteCommand(pRev); 8     //请求绑定命令 9     Invoker* pInv = new Invoker(pCmd);10     pInv->Invoke();11 12     return 0;13 }
复制代码

0 0
原创粉丝点击