命令模式简介
老样子,为了便于理解这个模式,先举个生活中类似的例子。比如,在使用手机买东西的时候,点了一下付款,然后输入密码就可以完成购物,在点击付款的时候,用户是不会在意手机后台调用了哪些函数,与后台发送了什么数据,进行了哪些安全的校验,同样,手机与后台也同样不知道,具体是谁在使用这个手机和软件,命令的发送方和接收方没有任何实际关联。
在设计模式中,命令模式就是解耦了命令的发送者和接受者,发送者和接受者之间没有直接引用关系,发送者只要知道如何发送请求,不必知道请求是如何完成的。
命令模式:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
命令模式结构
命令模式的结构如上图,命令模式一共有以下几种角色:
- Command(抽象命令类):是一个抽象类,声明了用于执行命令的接口execute()。
- ConcreteCommand(具体命令类):具体的命令类,实现了执行命令的接口execute(),它对应具体的接收者对象,将接收者(Receiver)的动作action()绑定其中。在execu()方法中将调用接收者的动作action()。(这就是定义中的“将请求封装成一个对象”的体现)
- Invoker(调用者):请求的发送者,通过命令对象来执行请求。一个调用者不需要在设计时确定其接收者,所以调用者通过聚合,与命令类产生关联。具体实现中,可以将一个具体命令对象注入到调用者中,再通过调用具体命令对象的execute()方法,实现简介请求命令执行者(接收者)的操作。
- Receiver(接收者): 实现处理请求的具体操作(action)。
命令模式代码实例
CommandPattern.h
#include <mutex>
#include <time.h>
using namespace std;
// 命令队列类
#define COMMAND_QUEUE
// 抽象命令类 Command
class Command
{
public:
Command() {}
// 声明抽象接口:发送命令
virtual void execute() = 0;
private:
Command *command;
};
// 接收者:电灯类
class Lamp
{
public:
Lamp()
{
this->lampState = false;
}
void on()
{
lampState = true;
cout << "Lamp is on\n";
}
void off()
{
lampState = false;
cout << "Lamp is off\n";
}
bool getLampState()
{
return lampState;
}
private:
bool lampState;
};
// 接收者:风扇类
class Fan
{
public:
Fan()
{
this->fanState = false;
}
void on()
{
fanState = true;
cout << "Fan is on\n";
}
void off()
{
fanState = false;
cout << "Fan is off\n";
}
bool getFanState()
{
return fanState;
}
private:
bool fanState;
};
// 具体命令类 LampCommand
class LampCommand : public Command
{
public:
LampCommand()
{
cout << "开关控制电灯\n";
lamp = new Lamp();
}
// 实现execute()
void execute()
{
if (lamp->getLampState())
{
lamp->off();
}
else
{
lamp->on();
}
}
private:
Lamp *lamp;
};
// 具体命令类 FanCommand
class FanCommand : public Command
{
public:
FanCommand()
{
cout << "开关控制风扇\n";
fan = new Fan();
}
// 实现execute()
void execute()
{
if (fan->getFanState())
{
fan->off();
}
else
{
fan->on();
}
}
private:
Fan *fan;
};
// 调用者 Button
class Button
{
public:
Button() {}
// 注入具体命令类对象
void setCommand(Command *cmd)
{
this->command = cmd;
}
// 发送命令:触摸按钮
void touch()
{
cout << "触摸开关:";
command->execute();
}
private:
Command *command;
};
#ifdef COMMAND_QUEUE
/*************************************/
/* 命令队列 */
#include <vector>
// 命令队列类
class CommandQueue
{
public:
CommandQueue() {}
void addCommand(Command *cmd)
{
commandQueue.push_back(cmd);
}
void execute()
{
for (int i = 0; i < commandQueue.size(); i++)
{
commandQueue[i]->execute();
}
}
private:
vector<Command *> commandQueue;
};
// 调用者
class Button2
{
public:
Button2() {}
// 注入具体命令队列类对象
void setCommandQueue(CommandQueue *cmdQueue)
{
this->cmdQueue = cmdQueue;
}
// 发送命令:触摸按钮
void touch()
{
cout << "触摸开关:";
cmdQueue->execute();
}
private:
CommandQueue *cmdQueue;
};
#endif
CommandPattern.cpp
#include <iostream>
#include "CommandPattern.h"
int main()
{
// 实例化调用者:按钮
Button *button = new Button();
Command *lampCmd, *fanCmd;
// 按钮控制电灯
lampCmd = new LampCommand();
button->setCommand(lampCmd);
button->touch();
button->touch();
button->touch();
cout << "\n";
// 按钮控制风扇
fanCmd = new FanCommand();
button->setCommand(fanCmd);
button->touch();
button->touch();
button->touch();
#ifdef COMMAND_QUEUE
cout << "\n***********************************\n";
Button2 *button2 = new Button2();
Command *lampCmd2, *fanCmd2;
CommandQueue *cmdQueue = new CommandQueue();
// 按钮控制电灯
lampCmd2 = new LampCommand();
cmdQueue->addCommand(lampCmd2);
// 按钮控制风扇
fanCmd2 = new FanCommand();
cmdQueue->addCommand(fanCmd2);
button2->setCommandQueue(cmdQueue);
button2->touch();
#endif
cout << "\n";
delete button;
delete lampCmd;
delete fanCmd;
delete button2;
delete lampCmd2;
delete fanCmd2;
return 0;
}
命令模式总结
优点:
- 降低系统耦合度,将命令的请求者与接收者分离解耦,请求者和发送者不存在直接关联,各自独立互不影响。
- 便于扩展:新的命令很容易加入到系统中,且符合开闭原则。
- 较容易实现命令队列或宏命令。
- 为请求的撤销和回复操作提供了一种设计实现方案。
缺点:
- 命令模式可能导致系统中有过多的具体命令类,增加了系统中对象的数量。
适用环境:
- 系统需要将请求发送者和接收者解耦,使得发送者和接收者互不影响。
- 系统需要在不同时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销和恢复操作。
- 系统需要将一组操作组合在一起形成宏命令。