设计模式专栏: http://t.csdnimg.cn/4Mt4u
相关系列文章
目录
1.概述
命令模式(Command Pattern)是对命令的封装,每一个命令都是一个请求操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行该操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接受,怎样被操作以及是否被执行等。命令模式属于行为型模式。
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请 求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。但紧耦合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只能修改源码。而命令模式通过为请求与实现间引入了一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。所以,命令模式的本质是解耦命令请求与处理。
2.结构
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法,其结构如图所示:

角色定义:
抽象命令类(Command)角色: 抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的
execute()
等方法,通过这些方法可以调用请求接收者的相关操作。
具体命令(Concrete Command)角色:具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()
方法时,将调用接收者对象的相关操作(action)
实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者(Invoker)角色 : 调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()
方法,从而实现间接调用请求接收者的相关操作。
**客户端(Client)角色:**创建具体命令对象并设置其接收者,将命令对象交给调用者执行。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
3.实现
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的 execute() 方法,每个具体命令类将一个 Receiver 类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了 execute() 方法的不同实现,并调用不同接收者的请求处理方法。
典型的抽象命令类代码如下:
cpp
class ICommand
{
public:
virtual ~ICommand() {}
virtual void execute() = 0;
};
请求接收者 Receiver 类具体实现对请求的业务处理,它提供了 action() 方法,用于执行与请求相关的操作,其典型代码如下:
cpp
class CReceiver
{
public:
void action() {
//具体操作
std::cout << "CReceiver::action..." << std::endl;
}
};
具体命令类继承了抽象命令类,它与请求接收者相关联,实现了在抽象命令类中声明的 execute() 方法,并在实现时调用接收者的请求响应方法 action(),代码如下:
cpp
class CConcreteCommand : public ICommand
{
public:
explicit CConcreteCommand(CReceiver* pReceiver) : m_pReceiver(pReceiver) {}
void execute() override {
m_pReceiver->action();
}
private:
CReceiver* m_pReceiver;
};
对于请求发送者即调用者而言,将针对抽象命令类进行编程,可以通过构造注入或者设值注入的方式在运行时传入具体命令类对象,并在业务方法中调用命令对象的 execute() 方法,其典型代码如下:
cpp
class CInvoker {
public:
CInvoker(ICommand* cmd) {
m_pCommand = cmd;
}
void setCommand(ICommand* cmd) { m_pCommand = cmd; }
// 业务方法 用于调用命令类的 execute() 方法
void doWork() {
m_pCommand->execute();
}
private:
ICommand* m_pCommand;
};
测试用例:
cpp
int main()
{
std::unique_ptr<CReceiver> pReceiver(new CReceiver);
std::unique_ptr<ICommand> pCommand(new CConcreteCommand(pReceiver.get()));
std::unique_ptr<CInvoker> pCaller(new CInvoker(pCommand.get()));
pCaller->doWork();
return 0;
}
输出:CReceiver::action...
4.使用场景
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.现实语义中具备"命令"的操作(如命令菜单、shell命令等)。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.系统需要在不同的时间指定请求、将请求排队和执行请求。
5.需要支持命令宏(即命令组合操作)。
5.总结
优点:
1.解耦:命令模式将请求的发送者和接收者解耦,请求的发送者不需要知道接收者的具体实现细节,只需要通过命令对象来发送请求。
2.可撤销操作:命令模式支持可撤销的操作,通过将命令对象存储在历史记录中或者在命令对象中实现撤销操作,可以实现操作的撤销。
3.日志记录:命令模式可以方便地记录请求的历史,例如将命令对象存储在日志中以便于后续的审计和回溯。
4.支持事务:命令模式可以支持事务,通过将多个命令组合成一个更大的操作,可以保证这些命令要么全部执行,要么全部不执行。
5.方便添加新操作:新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码甚至客户类代码,满足开闭原则的要求。
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。