产生背景
对一个对象要进行很多动作,每个动作就是一个命令。在原有的逻辑中,需要在客户端写出许多分支语句,针对每个动作,调用该对象的成员函数。每增加一个动作,都需要在客户端和对象类中修改代码。违背了高内聚,低耦合的原则。因此,对于这种情况就可以使用命令模式。
命令模式概述
定义
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求队列或者记录请求日志,可以提供命令的撤销和恢复功能。它属于行为型模式
UML图
角色
- 客户(Client):创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
- 命令(Command):声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
- 具体命令(ConcreteCommand):定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收者的相应操作。Execute()方法通常叫做执行方法。
- 调用者(Invoker):负责调用命令对象执行请求,相关的方法叫做行动方法。常会持有命令对象,可以持有很多的命令对象。
- 接收者(Receiver):负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
举例:
#include <iostream>
#include <vector>
using namespace std;
// 烤肉师傅(接收者)
class RoastCook
{
public:
void MakeMutton() { cout << "烤羊肉" << endl; }
void MakeChickenWing() { cout << "烤鸡翅" << endl; }
};
// 抽象命令类
class Command
{
public:
Command(RoastCook* temp) { receiver = temp; }
virtual void ExecuteCmd() = 0;
protected:
RoastCook* receiver;//包含接收者
};
// 烤羊肉命令
class MakeMuttonCmd : public Command
{
public:
MakeMuttonCmd(RoastCook* temp) : Command(temp) {}
virtual void ExecuteCmd() { receiver->MakeMutton(); }//调用接收者执行相应的具体命令
};
// 烤鸡翅命令
class MakeChickenWingCmd : public Command
{
public:
MakeChickenWingCmd(RoastCook* temp) : Command(temp) {}
virtual void ExecuteCmd() { receiver->MakeChickenWing(); }
};
// 服务员类(调用者)
class Waiter
{
public:
void SetCmd(Command* temp);
// 通知执行
void Notify();
protected:
vector<Command*> m_commandList;
};
void Waiter::SetCmd(Command* temp)
{
m_commandList.push_back(temp);
cout << "增加订单" << endl;
}
void Waiter::Notify()
{
vector<Command*>::iterator it;
for (it = m_commandList.begin(); it != m_commandList.end(); ++it)
{
(*it)->ExecuteCmd();
}
}
int main()
{
// 店里添加烤肉师傅、菜单、服务员等顾客
RoastCook* cook = new RoastCook();
Command* cmd1 = new MakeMuttonCmd(cook);
Command* cmd2 = new MakeChickenWingCmd(cook);
Waiter* waitess = new Waiter();
// 点菜
waitess->SetCmd(cmd1);
waitess->SetCmd(cmd2);
// 服务员通知
waitess->Notify();
return 0;
}
优点
- 降低系统的耦合度:Command模式将调用操作的对象与知道如何实现该操作的对象解耦。发布者只需要发布命令就可以,无需知道执行者是谁,发布者和执行者可以独立的进行修改
- Command是头等的对象。它们可像其他的对象一样被操纵和扩展。
- 组合命令:你可将多个命令装配成一个组合命令,即可以比较容易地设计一个命令队列和宏命令。一般说来,组合命令是Composite模式的一个实例。
- 增加新的Command很容易,因为这无需改变已有的类。
- 可以方便地实现对请求的Undo和Redo。系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
举例:
假设有一个快餐店,而我是该餐厅的点餐服务员,那么我一天的工作应该是这样的:当某位客人点餐或者打来订餐电话后,我会把他的需求都写在清单上,然后交给厨房,客人不用关心是哪些厨师帮他炒菜。我们餐厅还可以满足客人需要的定时服务,比如客人可能当前正在回家的路上,要求 1个小时后才开始炒他的菜,只要订单还在,厨师就不会忘记。客人也可以很方便地打电话来撤销订单。另外如果有太多的客人点餐,厨房可以按照订单的顺序排队炒菜。这些记录着订餐信息的清单,便是命令模式中的命令对象。
缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
适用场景
- 需要在不同的时间制定请求、将请求排队
- 系统需要支持命令的撤销
- 如果要将系统中所有的数据更新到日志里。以便在系统崩溃时,可以根据日志读回所有的数据更新命令