1.命令模式的定义及类图
1.1 命令模式的定义
传统的类调用别的类的过程分为三步:创建目标对象的实例;设置调用所需类的函数;调用目标对象的方法。而Command模式即为使用一个专门的类对这种调用过程加以封装,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
其主要适用于将一个请求封装为一个对象(类),从而可以使用不同的请求对客户端进行参数化;可以对这些请求进行排队或记录请求日志的同时,也可以支持增删等操作。
1.2 命令模式的类图
其类图如下:
关于命令模式的类图,网上的版本各有千秋,主要体现在Invoker、Receive和Concrete三个类与Client的关系上。综合比较后,我认为Client客户端与Invoker调用者,为实打实的关联关系,而非传统的依赖关系;而Client客户端与Receive执行者应为依赖关系;同时,Client客户端与具体命令类ConcreteCommand仍保持依赖关系。
我个人认为的原因如下:
- Invoker调用者,对于客户端来说,其作为命令的收集者以及命令执行的发起者,其作为命令模式实现功能的关键,必须保证其在Client的函数中被实例化,并使用其起到作用,这是一种长期性的,非偶然性使用的关系,所以两者之间的关系应为关联关系。具体来说,是Client客户端对Invoker调用者的单向关联关系;
- Receive执行者,对于客户端来说,可能有的执行者千千万,比如你可以让小刘去做个Word,也可以让小李去做个Word,客户端调用哪个执行者,是存在一定的偶然性的。同时,对应的执行者在完成任务后,也会暂时失去与客户端的联系,可见两者关系是一种临时性的,可见两者关系应为依赖关系(当然,还有一种需要和ConcreteCommand一起用到的情况,在下面具体描述)。具体来说,是Client客户端对Receive执行者的单向依赖关系。
- ConcreteCommand具体命令,对于客户端来说,所调用的Invoker调用者中存在多少个命令,这些命令有多少种类,其实其并不关心,其重点是调用Invoker调用者来将所存储的命令分发出去执行,至于这些任务是什么,与客户端其实是没有关系的。但是,在对于Invoker添加了增删命令等操作之后,可能需要Receive执行者和ConcreteCommand具体命令来实现增删过程,此时两者都是作为局部变量的状态出现在客户端中,在执行完增删等任务之后就与客户端毫无联系,所以Client类和ConcreteCommand类也是一种临时的,偶然的关系,故两者为依赖关系。具体来说,是Client客户端对ConcreteCommand具体命令的单向依赖关系。
那么在理清楚这些关系后,再来审视这个类图:
- Invoker类,为命令的收集者与发布者,这代表其中必然有存储命令的数据结构,也有相应的执行命令的相关函数。同时其与Command接口类为聚合关系,可知Command接口类的实例化数据会被存储到其中的数据结构中(无论是单个对象,或者list类的容器结构),可以对这些命令进行增删与记录等操作;
- Command接口类:为命令的接口类,负责定义项目中所需例子的接口函数,为所有的Command类提供预定义的过程;
- ConreteCommand类:为命令的实例化类,其通过继承Command接口,重载其中的虚函数实现所需的函数功能(无论是定义所需要执行的对象Receive类,还是记录每次需执行命令的日志等);
- Receive类:为命令的实际执行者,其在实例化后,会被各个ConreteCommand类所调用,从而实现命令到执行的绑定过程。而其可能有多种种类,并且可能一个命令不止一个种类去执行;
- Client类:为客户端,其在调用时只需要new出Invoker类作为调用者,对存储在其中的命令进行发布即可。对于Invoker类的增删命令等操作,可以在客户端中进行,也可以在底层函数进行(当在客户端进行时,其与Receive类和ConreteCommand类成依赖关系)
2.命令模式的代码实现
本例中的Invoker类为Boss类,表示负责收集命令与发布的老板;而Command类为Make_Office类,表示一个制作Office文件的接口,所以其对应的具体ConreteCommand实例类即为Make_Word类、Make_Excel类以及Make_Powerpoint类,而负责执行这些命令的为Staff类,代表执行老板命令的公司职员。
最后在主函数中new出需要使用的Staff类的公司职员,ConreteCommand类的老板命令以及Invoker类的大老板。值得一提的是,主函数中的东西不能全部代表客户端类Client,因为有些操作其实是可以在别的地方完成的。比如对于Boss类的赋值过程,都可以在别的地方实现,主函数只需要使用Boss类的SubmitCommand函数发布命令即可。
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Staff
{
public:
Staff(string name) : m_name(name) {}
void Make_Word()
{
cout << "Boss command staff " << m_name << " to make a word!" << endl;
}
void Make_Excel()
{
cout << "Boss command staff " << m_name << " to make a excel!" << endl;
}
void Make_Powerpoint()
{
cout << "Boss command staff " << m_name << " to make a ppt!" << endl;
}
private:
string m_name;
};
class Command
{
public:
virtual void Make_Office() = 0;
};
class Word_Command : public Command
{
public:
Word_Command(Staff* staff) : m_staff(staff) {}
void Make_Office()
{
m_staff->Make_Word();
}
private:
Staff* m_staff;
};
class Excel_Command : public Command
{
public:
Excel_Command(Staff* staff) : m_staff(staff) {}
void Make_Office()
{
m_staff->Make_Excel();
}
private:
Staff* m_staff;
};
class Powerpoint_Command : public Command
{
public:
Powerpoint_Command(Staff* staff) : m_staff(staff) {}
void Make_Office()
{
m_staff->Make_Powerpoint();
}
private:
Staff* m_staff;
};
class Boss
{
public:
Boss()
{
m_commandlist.clear();
}
void AddCommand(Command* command)
{
m_commandlist.push_back(command);
}
void RemoveCommand(Command* command)
{
m_commandlist.remove(command);
}
void SubmitCommand()
{
for (auto &s : m_commandlist)
{
s->Make_Office();
}
m_commandlist.clear();
}
private:
list<Command*> m_commandlist;
};
int main()
{
Staff* mike = new Staff("mike");
Staff* lily = new Staff("lily");
Word_Command* word1 = new Word_Command(mike);
Excel_Command* excel1 = new Excel_Command(mike);
Powerpoint_Command* ppt1 = new Powerpoint_Command(lily);
Boss* jack = new Boss;
jack->AddCommand(word1);
jack->AddCommand(excel1);
jack->AddCommand(ppt1);
jack->SubmitCommand();
jack->AddCommand(word1);
jack->AddCommand(excel1);
jack->AddCommand(ppt1);
jack->RemoveCommand(excel1);
jack->SubmitCommand();
delete mike;
delete lily;
delete word1;
delete excel1;
delete ppt1;
delete jack;
return 0;
}