## 模式定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
## 模式动机
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
## 模式结构
- **Command**: 抽象命令类
- **ConcreteCommand**: 具体命令类
- **Invoke**r: 调用者
- **Receiver**: 接收者
- **Client**:客户类

## 应用实例
1. 电视机遥控器
电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。

## 代码分析
模拟了一个网络通信的案例。在网络中服务器每次只能处理一个请求, 当请求过多,服务器会把请求放入任务队列逐一取出处理。服务器与客户端在建立好 `SOCKET` 连接后,会发送一系列的协议用于告诉服务器请求的是那些服务,`HandleClientProtocol` 类内部封装了一些列用于处理客户端不同协议的不同处理方法,例如添加金币、钻石等。 但是服务器如何将这些函数封装进入任务队列呢?函数指针?可以选择将这些请求参数化,即封装成一个对象,每个对象都有一个抽象的接口 `AbstractCommand`,针对与每一个不同的请求,都将协议处理的总方法`HandleClientProtocol`作为一个成员参数,该请求调用需要调用的请求即可。同时将该请求的这个具体类放入任务队列中即可。
```cpp
//请求处理方法
class HandleClientProtocol {
public:
void AddMoney() { cout << "给玩家增加金币!" << endl; }
void AddDiamond() { cout << "给玩家增加钻石!" << endl; }
void AddEipment() { cout << "给玩家增加装备!" << endl; }
void AddLevel() { cout << "给玩家升级!" << endl; }
};
//命令接口
class AbstractCommand {
public:
virtual void handle() = 0;
};
//针对每一个请求,具体化对象,封装成一个类,统一任务接口
class AddMoneyCommand :public AbstractCommand {
public:
AddMoneyCommand(HandleClientProtocol* protocol) :pProtocol(protocol) {}
virtual void handle() { pProtocol->AddMoney(); }
private:
HandleClientProtocol* pProtocol;
};
class AddDiamondCommand :public AbstractCommand {
public:
AddDiamondCommand(HandleClientProtocol* protocol) :pProtocol(protocol) {}
virtual void handle() { pProtocol->AddDiamond(); }
private:
HandleClientProtocol* pProtocol;
};
class AddEipmentCommand :public AbstractCommand {
public:
AddEipmentCommand(HandleClientProtocol* protocol) :pProtocol(protocol) {}
virtual void handle() { pProtocol->AddEipment(); }
private:
HandleClientProtocol* pProtocol;
};
class AddLevelCommand :public AbstractCommand {
public:
AddLevelCommand(HandleClientProtocol* protocol) :pProtocol(protocol) {}
virtual void handle() { pProtocol->AddLevel(); }
private:
HandleClientProtocol* pProtocol;
};
//模拟服务器
class Server {
public:
void addRequest(AbstractCommand* command) { mCommands.push(command); } //添加任务
void StartHandle() { //处理任务
while (!mCommands.empty()) {
AbstractCommand* nowCommand = mCommands.front();
mCommands.pop();
nowCommand->handle();
delete nowCommand;
nowCommand = nullptr;
Sleep(2000); //模拟服务器处理,每两秒处理一个
}
}
~Server() {
while (!mCommands.empty()) {
AbstractCommand* nowCommand = mCommands.front();
mCommands.pop();
delete nowCommand;
nowCommand = nullptr;
}
}
private:
queue<AbstractCommand*> mCommands; //任务调度队列
};
void test1() {
Server* server = new Server;
HandleClientProtocol* protocol = new HandleClientProtocol;
AbstractCommand *addMoney = new AddMoneyCommand(protocol);
AbstractCommand* addDiamond = new AddDiamondCommand(protocol);
AbstractCommand* addeipment = new AddEipmentCommand(protocol);
AbstractCommand* addlevel = new AddLevelCommand(protocol);
server->addRequest(addMoney);
server->addRequest(addDiamond);
server->addRequest(addeipment);
server->addRequest(addlevel);
server->StartHandle();
}
```
针对命令模式的定义,需要对任务有撤销操作,目前想到的方法是再添加一个栈,队列一边弹,栈一边入,找到以后把栈里面的元素倒回去,时间复杂度很高,比较简单就不实现了,肯定有更好的方法。
## 命令模式的优缺点
### 优点
- 降低系统的耦合度
- 新的命令可以很容易地加入到系统中
- 可以比较容易地设计一个命令队列和宏命令(组合命令)
- 可以方便地实现对请求的 Undo(撤销) 和 Redo(恢复)。
### 缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用
## 适用环境
1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
2. 系统需要在不同的时间指定请求、将请求排队和执行请求
3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4. 系统需要将一组操作组合在一起,即支持宏命令
行为模式之命令模式