定义
命令模式(Command Pattern) 是一种行为型设计模式,它将请求封装成一个对象,从而使您可以使用不同的请求、队列、日志请求以及支持撤销操作等功能。命令模式通过将请求(命令)封装成对象,使得客户端可以将请求发送者与请求接收者解耦,从而更灵活地控制操作的执行。
特性
- 命令对象:将请求封装成一个命令对象,该命令对象包含了执行的具体操作。
- Invoker(调用者):调用命令对象的 execute() 方法来执行相应的操作。
- Receiver(接收者):实际执行命令的对象。
- Client(客户端):客户端创建命令对象并设置命令的接收者。
- Command(命令接口):定义命令接口,声明执行操作的抽象方法。
命令模式使得我们能够通过不同的命令对象来执行不同的操作,且操作的执行由调用者控制。
场景
适用场景
- 请求调用者与请求接收者解耦:当客户端希望通过发送请求来调用不同的操作,而不希望知道具体如何执行时,可以使用命令模式。
- 需要参数化对象:当需要参数化对象进行命令的请求时,命令模式可以封装请求的参数。
- 支持撤销和恢复操作:命令模式非常适合实现撤销和恢复操作,通过存储命令对象及其执行过程,能够轻松地实现撤销功能。
- 支持队列或日志请求:命令模式可以将请求封装成对象,方便将多个命令排入队列或记录日志。
应用场景
- 图形用户界面(GUI)中的按钮点击事件:通过命令模式将按钮的点击事件封装为命令,使得不同按钮的操作可以被独立控制。
- 事务管理系统:在事务管理中,命令可以表示一系列操作,通过命令模式进行回滚或恢复。
- 多操作处理系统:在系统中可能有多个操作(如编辑操作),使用命令模式可以统一管理操作。
类设计
命令模式通常包括以下几个角色:
- Command(命令接口):声明执行操作的抽象方法。
- ConcreteCommand(具体命令类):实现了 Command 接口,封装了具体的请求与接收者之间的关系。
- Receiver(接收者):负责执行与请求相关的操作。
- Invoker(调用者):调用命令对象来执行请求。
- Client(客户端):客户端创建命令对象并设置接收者。
代码实现
我们设计一个 遥控器操作 的例子。遥控器上有多个按钮,每个按钮对应一个操作(如打开电视、关闭空调等)。我们使用命令模式来封装每个按钮的操作,并通过遥控器(调用者)来执行这些操作。
1. 定义命令接口(Command)
#include <iostream>
#include <string>
using namespace std;
// 命令接口
class Command {
public:
virtual void execute() = 0; // 执行命令的接口
virtual ~Command() {}
};
- Command 是命令接口,声明了 execute() 方法,该方法将由具体命令类来实现。
2. 定义具体命令类(ConcreteCommand)
// 电视打开命令
class TVOnCommand : public Command {
private:
class TV* tv; // 接收者(电视)
public:
TVOnCommand(class TV* tv) : tv(tv) {}
void execute() override {
tv->turnOn(); // 执行命令:打开电视
}
};
// 电视关闭命令
class TVOffCommand : public Command {
private:
class TV* tv;
public:
TVOffCommand(class TV* tv) : tv(tv) {}
void execute() override {
tv->turnOff(); // 执行命令:关闭电视
}
};
// 空调开命令
class ACOnCommand : public Command {
private:
class AC* ac;
public:
ACOnCommand(class AC* ac) : ac(ac) {}
void execute() override {
ac->turnOn(); // 执行命令:打开空调
}
};
// 空调关命令
class ACOffCommand : public Command {
private:
class AC* ac;
public:
ACOffCommand(class AC* ac) : ac(ac) {}
void execute() override {
ac->turnOff(); // 执行命令:关闭空调
}
};
- 每个命令类(如 TVOnCommand、TVOffCommand 等)都实现了 Command 接口,并封装了具体的操作逻辑。
- 每个命令类持有一个接收者(例如 TV 或 AC),并在 execute() 方法中调用接收者的方法执行具体的操作。
3. 定义接收者类(Receiver)
class TV {
public:
void turnOn() {
cout << "TV is turned ON." << endl;
}
void turnOff() {
cout << "TV is turned OFF." << endl;
}
};
class AC {
public:
void turnOn() {
cout << "AC is turned ON." << endl;
}
void turnOff() {
cout << "AC is turned OFF." << endl;
}
};
- TV 和 AC 类是接收者,包含了执行具体操作的方法(例如打开电视、关闭空调)。
4. 定义调用者类(Invoker)
class RemoteControl {
private:
Command* command; // 持有命令对象
public:
void setCommand(Command* command) {
this->command = command; // 设置命令对象
}
void pressButton() {
command->execute(); // 执行命令
}
};
- RemoteControl 类是调用者,持有一个命令对象并在按下按钮时执行该命令。
5. 客户端调用
int main() {
// 创建接收者对象
TV* tv = new TV();
AC* ac = new AC();
// 创建命令对象
Command* tvOn = new TVOnCommand(tv);
Command* tvOff = new TVOffCommand(tv);
Command* acOn = new ACOnCommand(ac);
Command* acOff = new ACOffCommand(ac);
// 创建遥控器
RemoteControl* remote = new RemoteControl();
// 按下按钮打开电视
remote->setCommand(tvOn);
remote->pressButton();
// 按下按钮关闭电视
remote->setCommand(tvOff);
remote->pressButton();
// 按下按钮打开空调
remote->setCommand(acOn);
remote->pressButton();
// 按下按钮关闭空调
remote->setCommand(acOff);
remote->pressButton();
// 清理内存
delete tv;
delete ac;
delete tvOn;
delete tvOff;
delete acOn;
delete acOff;
delete remote;
return 0;
}
6. 输出结果
TV is turned ON.
TV is turned OFF.
AC is turned ON.
AC is turned OFF.
- 客户端通过 RemoteControl 类来控制设备的开关,每次按下按钮时,遥控器都会调用相应命令对象的 execute() 方法,来完成实际的操作。
命令模式的优缺点
优点
- 解耦发送者和接收者:命令模式将请求的发送者和接收者解耦,客户端不需要知道谁会处理请求,只需要发送命令对象即可。
- 支持撤销和恢复:命令模式可以很容易实现撤销和恢复操作,命令对象可以保存执行过程,支持回滚。
- 命令队列和日志:命令可以存储在队列中或日志中,方便管理和回溯。
- 可扩展性:新命令的增加不会影响现有代码,只需要新增具体命令类即可。
缺点
- 类的数量增加:每个命令都会对应一个具体的命令类,可能会增加类的数量。
- 系统结构复杂:使用命令模式时,系统中需要管理多个命令类、调用者和接收者,可能使系统结构变得复杂。
场景
适用场景:
- GUI事件处理:例如,按钮点击、菜单选择等GUI事件的处理,可以通过命令模式将每个事件封装为命令对象。
- 任务调度系统:将任务封装成命令对象,通过队列或调度器执行任务。
- 撤销/恢复功能:如文本编辑器、绘图软件等,需要提供撤销和重做的功能,命令模式能很方便地实现该功能。
- 宏命令:多个命令可以组合成一个“宏命令”,一起执行。
应用场景:
- 文本编辑器的撤销操作:用户进行文本编辑时,编辑操作可以封装为命令对象,撤销时,可以通过命令对象来恢复到之前的状态。
- 图形编辑器中的操作:在图形编辑器中,用户可以执行绘制、删除、移动等操作,每个操作都可以封装为命令对象,便于撤销和重做。
- 网络请求处理:网络请求可以封装为命令对象,每个请求可以通过命令对象来执行,便于管理请求的执行顺序和状态。
总结
命令模式通过将请求封装成命令对象,使得请求的发送者与接收者解耦。它可以帮助简化系统中的操作,支持撤销/恢复功能,并使得系统更具扩展性。命令模式适用于事件处理、任务调度、宏命令等场景,可以使系统的操作更加灵活和可管理。