概念
命令模式是一种行为型设计模式,它将请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
定义
命令模式把一个请求或者操作封装到一个对象中,将发出命令的责任和执行命令的责任分割开。这样,命令的发送者不需要知道命令的接收者是谁,也不需要知道命令是如何被执行的,只需要关心如何发出命令。而命令的接收者只需要专注于如何执行命令,实现具体的业务逻辑。
想象一下,此时你手里握着一个智能家居的遥控器,可以控制家里的各种设备(灯、空调、音响)。最初,遥控器的每个按钮直接绑定了设备的操作------比如"开灯"按钮直接硬编码调用了客厅灯.turnOn()
的方法。这种方法看似简单,但是当设备升级的时候(比如换用语音控制的智能灯),你就需要拆开遥控器重新焊接电路;想新增一个"观影模式"(开灯、开空调、关窗帘)时,你又需要继续再遥控器内新增一堆新逻辑。而命令模式(Command) 则是把每一个操作(如开灯、调节温度)封装成独立的 命令对象 ,例如LightOnCommand
包含执行(execute()
)和撤销(undo()
)方法,内部持有对灯具的引用。遥控器(调用者)完全无需知道设备细节,只需存储并触发这些命令对象。当你按下"开灯"按钮时,遥控器只是调用lightCommand.execute()
,具体是传统灯具还是智能灯执行,它毫不关心。
那么,在这样的设计模式下,就会体现出下述三个优点:
- 灵活扩展 :新增空调控制,只需创建一个
AirconCommand
丢给遥控器,无需改动原有代码; - 支持宏命令 :将关灯、开空调、降窗帘组合成
MovieModeCommand
,一键触发复杂操作; - 实现撤销功能:执行命令后,遥控器记录历史,按下"撤销"键即可回退到上一步状态。
这就是命令模式的核心------将"请求"抽象为对象,让调用者和接收者解耦。
组成部分
- 命令接口(Command) :声明了执行命令的抽象方法,所有具体命令类都需要实现这个接口。它定义了一个统一的执行命令的方法,通常命名为
execute
等,用来规范具体命令类的行为。 - 具体命令类(ConcreteCommand) :实现了命令接口,持有一个接收者对象的引用,在
execute
方法中调用接收者的相关方法来完成具体的操作。它将命令的执行和接收者的具体行为绑定在一起,实现了命令的具体逻辑。 - 接收者(Receiver):知道如何执行与请求相关的操作,具体命令对象会调用它的方法来完成命令的执行。它是真正执行命令的对象,负责实现命令所对应的具体业务逻辑。
- 调用者(Invoker) :负责调用命令对象的
execute
方法来发起命令。它不直接与接收者交互,而是通过命令对象来间接执行操作,它可以设置命令对象,并在需要的时候触发命令的执行。 - 客户端(Client):创建具体命令对象,并设置命令对象的接收者。在客户端中,将命令的发送者和接收者进行解耦,使得发送者不需要了解接收者的具体实现,只需要关心命令的发送。

工作原理
- 定义接口 :先定义一个命令接口,声明
execute
等方法规范命令行为。 - 创建具体命令类 :创建实现命令接口的具体命令类,内部持有接收者对象,在
execute
方法中调用接收者方法完成具体操作。 - 设置接收者:创建接收者对象,它负责执行具体业务逻辑,具体命令类将其作为成员变量持有。
- 设置调用者 :创建调用者,它持有命令对象,通过调用命令对象的
execute
方法来发起命令,并不直接与接收者交互。 - 客户端操作:客户端负责创建具体命令对象,设置好接收者,并将命令对象传递给调用者。
- 命令执行:调用者执行命令时,命令对象调用接收者的相应方法完成操作,实现请求发送者和接收者的解耦。若支持撤销,命令对象会记录执行前状态,在撤销时调用接收者方法恢复状态。
示例
我么使用智能家居遥控器的例子来编写示例代码。
类图

C++实现
C++
#include <iostream>
#include <vector>
// 前向声明
class Receiver;
// 命令接口
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// 接收者:灯具
class Light {
public:
void turnOn() {
std::cout << "Light is on." << std::endl;
}
void turnOff() {
std::cout << "Light is off." << std::endl;
}
};
// 接收者:空调
class AirConditioner {
public:
void turnOn() {
std::cout << "Air conditioner is on." << std::endl;
}
void turnOff() {
std::cout << "Air conditioner is off." << std::endl;
}
};
// 开灯命令
class LightOnCommand : public Command {
private:
Light* light;
public:
explicit LightOnCommand(Light* light) : light(light) {}
void execute() override {
light->turnOn();
}
void undo() override {
light->turnOff();
}
};
// 关灯命令
class LightOffCommand : public Command {
private:
Light* light;
public:
explicit LightOffCommand(Light* light) : light(light) {}
void execute() override {
light->turnOff();
}
void undo() override {
light->turnOn();
}
};
// 开空调命令
class AirConditionerOnCommand : public Command {
private:
AirConditioner* ac;
public:
explicit AirConditionerOnCommand(AirConditioner* ac) : ac(ac) {}
void execute() override {
ac->turnOn();
}
void undo() override {
ac->turnOff();
}
};
// 关空调命令
class AirConditionerOffCommand : public Command {
private:
AirConditioner* ac;
public:
explicit AirConditionerOffCommand(AirConditioner* ac) : ac(ac) {}
void execute() override {
ac->turnOff();
}
void undo() override {
ac->turnOn();
}
};
// 调用者:遥控器
class RemoteControl {
private:
std::vector<Command*> commands;
std::vector<Command*> undoCommands;
public:
void setCommand(Command* command) {
commands.push_back(command);
}
void pressButton(int index) {
if (index < commands.size()) {
commands[index]->execute();
undoCommands.push_back(commands[index]);
}
}
void pressUndoButton() {
if (!undoCommands.empty()) {
Command* lastCommand = undoCommands.back();
lastCommand->undo();
undoCommands.pop_back();
}
}
~RemoteControl() {
for (auto command : commands) {
delete command;
}
}
};
// 客户端代码
int main() {
// 创建接收者
Light light;
AirConditioner ac;
// 创建命令
Command* lightOn = new LightOnCommand(&light);
Command* lightOff = new LightOffCommand(&light);
Command* acOn = new AirConditionerOnCommand(&ac);
Command* acOff = new AirConditionerOffCommand(&ac);
// 创建调用者
RemoteControl remote;
// 设置命令
remote.setCommand(lightOn);
remote.setCommand(lightOff);
remote.setCommand(acOn);
remote.setCommand(acOff);
// 执行命令
remote.pressButton(0); // 开灯
remote.pressButton(2); // 开空调
remote.pressUndoButton(); // 撤销开空调
remote.pressUndoButton(); // 撤销开灯
return 0;
}
Java实现
Java
// 命令接口
interface Command {
void execute();
void undo();
}
// 接收者:灯具
class Light {
public void turnOn() {
System.out.println("Light is on.");
}
public void turnOff() {
System.out.println("Light is off.");
}
}
// 接收者:空调
class AirConditioner {
public void turnOn() {
System.out.println("Air conditioner is on.");
}
public void turnOff() {
System.out.println("Air conditioner is off.");
}
}
// 开灯命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
@Override
public void undo() {
light.turnOff();
}
}
// 关灯命令
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public void undo() {
light.turnOn();
}
}
// 开空调命令
class AirConditionerOnCommand implements Command {
private AirConditioner ac;
public AirConditionerOnCommand(AirConditioner ac) {
this.ac = ac;
}
@Override
public void execute() {
ac.turnOn();
}
@Override
public void undo() {
ac.turnOff();
}
}
// 关空调命令
class AirConditionerOffCommand implements Command {
private AirConditioner ac;
public AirConditionerOffCommand(AirConditioner ac) {
this.ac = ac;
}
@Override
public void execute() {
ac.turnOff();
}
@Override
public void undo() {
ac.turnOn();
}
}
// 调用者:遥控器
class RemoteControl {
private java.util.ArrayList<Command> commands = new java.util.ArrayList<>();
private java.util.ArrayList<Command> undoCommands = new java.util.ArrayList<>();
public void setCommand(Command command) {
commands.add(command);
}
public void pressButton(int index) {
if (index < commands.size()) {
Command command = commands.get(index);
command.execute();
undoCommands.add(command);
}
}
public void pressUndoButton() {
if (!undoCommands.isEmpty()) {
Command lastCommand = undoCommands.remove(undoCommands.size() - 1);
lastCommand.undo();
}
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
AirConditioner ac = new AirConditioner();
// 创建命令
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
Command acOn = new AirConditionerOnCommand(ac);
Command acOff = new AirConditionerOffCommand(ac);
// 创建调用者
RemoteControl remote = new RemoteControl();
// 设置命令
remote.setCommand(lightOn);
remote.setCommand(lightOff);
remote.setCommand(acOn);
remote.setCommand(acOff);
// 执行命令
remote.pressButton(0); // 开灯
remote.pressButton(2); // 开空调
remote.pressUndoButton(); // 撤销开空调
remote.pressUndoButton(); // 撤销开灯
}
}
代码解释
- Command 接口 :定义了命令的基本操作,包括
execute()
用于执行命令和undo()
用于撤销命令。 - 接收者类(Light 和 AirConditioner) :这些类表示具体的设备,包含设备的基本操作方法,如
turnOn()
和turnOff()
。 - 具体命令类(LightOnCommand、LightOffCommand、AirConditionerOnCommand、AirConditionerOffCommand) :实现了
Command
接口,内部持有对接收者的引用,在execute()
方法中调用接收者的相应操作,undo()
方法则执行相反的操作。 - 调用者类(RemoteControl) :负责存储和执行命令,通过
setCommand()
方法设置命令,pressButton()
方法执行指定索引的命令,pressUndoButton()
方法撤销上一个执行的命令。 - 客户端代码(main 函数):创建接收者、命令和调用者对象,设置命令并执行相应操作。
通过这种方式,遥控器(调用者)不需要知道具体设备的细节,只需要操作命令对象,提高了系统的可扩展性和可维护性。当设备升级或新增功能时,只需要创建新的命令类,而不需要修改遥控器的代码。
设计原则
命令模式主要遵循以下几个重要的设计原则:
单一职责原则(Single Responsibility Principle)
- 解释:该原则强调一个类应该仅有一个引起它变化的原因。在命令模式中,每个类都有其明确的职责。
- 具体体现
- 命令接口和具体命令类 :
Command
接口只负责定义命令的执行和撤销等操作规范,而具体的命令类(如LightOnCommand
、LightOffCommand
)只负责实现特定的命令逻辑,比如在execute
方法中调用接收者的特定操作,它们不涉及其他无关的功能。 - 接收者类 :像
Light
或AirConditioner
类,只专注于自身设备的具体操作,如开启、关闭等,不参与命令的管理和调用逻辑。 - 调用者类 :
RemoteControl
类只负责存储和执行命令,不关心命令具体如何实现以及接收者的具体操作细节。
- 命令接口和具体命令类 :
开闭原则(Open/Closed Principle)
- 解释:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即当需求发生变化时,应该通过扩展软件实体的行为来实现,而不是修改已有的代码。
- 具体体现
- 新增命令 :当需要添加新的命令时,比如增加一个调节灯光亮度的命令,只需要创建一个新的具体命令类(如
LightBrightnessCommand
),实现Command
接口,而不需要修改现有的Command
接口、调用者类(RemoteControl
)和接收者类(Light
)。 - 新增接收者 :如果要引入新的设备,如智能窗帘,只需要创建新的接收者类(
Curtain
)和对应的命令类(如CurtainOpenCommand
、CurtainCloseCommand
),原有的系统结构和代码基本无需改动。
- 新增命令 :当需要添加新的命令时,比如增加一个调节灯光亮度的命令,只需要创建一个新的具体命令类(如
依赖倒置原则(Dependency Inversion Principle)
- 解释:高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
- 具体体现
- 调用者与命令的关系 :
RemoteControl
作为高层模块,不直接依赖具体的命令类(如LightOnCommand
),而是依赖抽象的Command
接口。这样,当具体命令类发生变化时,RemoteControl
类不受影响。 - 具体命令类与接收者的关系 :具体命令类依赖于接收者的抽象(虽然这里接收者没有显式的抽象,但可以理解为依赖接收者的操作接口),而不是具体的接收者实现细节。例如,
LightOnCommand
只关心Light
类有turnOn
方法,而不关心Light
类内部具体如何实现开启灯光的操作。
- 调用者与命令的关系 :
迪米特法则(Law of Demeter)
- 解释:一个对象应该对其他对象有最少的了解,即一个类应该尽量减少与其他类之间的交互,只与直接的朋友通信。
- 具体体现
- 调用者与接收者的隔离 :
RemoteControl
作为调用者,它只与Command
对象交互,不需要了解接收者(如Light
、AirConditioner
)的具体实现。通过命令对象作为中间层,降低了调用者与接收者之间的耦合度。 - 具体命令类的独立性 :具体命令类(如
LightOnCommand
)只与它的直接朋友(Light
接收者和Command
接口)进行交互,不与其他无关的类产生不必要的联系。
- 调用者与接收者的隔离 :
命令模式的优缺点
优点
1. 解耦请求发送者和接收者
- 发送者(如调用者类)不需要知道接收者(如具体执行操作的对象)是谁,也不需要了解接收者的具体实现细节。发送者只需要通过命令对象来间接执行操作,降低了系统的耦合度。例如在智能家居系统中,遥控器(发送者)不需要知道灯具(接收者)的具体电路和控制方式,只需要触发相应的命令对象即可。
2. 可扩展性强
- 方便添加新的命令,只需要创建新的具体命令类并实现命令接口即可,不会影响到现有的其他类和模块。当系统需要新增功能时,如在智能家居系统中增加调节窗帘开合的功能,只需创建新的命令类和对应的接收者类,而无需修改遥控器(调用者)的代码。
3. 支持命令的撤销和重做
- 可以在命令对象中记录命令执行前的状态,在需要撤销时恢复到之前的状态,重做时再次执行命令。这在许多应用场景中非常有用,如文本编辑器中的撤销和重做操作,通过命令模式可以很方便地实现。
4. 便于实现命令队列和日志记录
- 可以将命令对象放入队列中,按照顺序依次执行,实现命令的排队执行。同时,也可以方便地记录命令的执行日志,便于系统的监控和故障排查。例如在多任务处理系统中,可以将一系列命令放入队列中,按顺序依次处理。
5. 符合开闭原则
- 对扩展开放,对修改关闭。当需要增加新的命令时,只需要扩展新的具体命令类,而不需要修改现有的调用者和其他命令类,提高了系统的可维护性。
缺点
1. 类的数量增加
- 每个具体的命令都需要创建一个对应的具体命令类,当系统中的命令较多时,会导致类的数量急剧增加,增加了系统的复杂性和维护成本。例如在一个复杂的游戏系统中,各种不同的操作都需要封装成命令类,可能会产生大量的类文件。
2. 命令过多时管理复杂
- 如果系统中有大量的命令,管理这些命令对象会变得困难。需要有良好的组织和管理机制,否则会导致代码混乱,降低代码的可读性和可维护性。
3. 增加系统的理解难度
- 由于引入了命令对象、调用者、接收者等多个角色和层次,对于不熟悉命令模式的开发者来说,理解和掌握系统的整体架构和工作流程会有一定的难度。
4. 性能开销
- 每个命令都封装成一个对象,会带来一定的内存开销和对象创建、销毁的性能开销。特别是在对性能要求较高的系统中,这种开销可能会成为一个问题。
注意事项
设计层面
- 合理设计命令接口
- 命令接口应简洁且具有通用性,只包含必要的方法,如
execute()
和undo()
。过多的方法会增加具体命令类的实现复杂度,破坏单一职责原则。 - 接口的命名要清晰,能准确表达命令的基本操作,方便其他开发者理解和使用。
- 命令接口应简洁且具有通用性,只包含必要的方法,如
- 控制类的数量
- 命令模式可能会导致类的数量增多,尤其是在系统功能复杂、命令多样的情况下。要合理规划命令类的层次结构,避免类的泛滥。可以通过抽象命令类或命令组来减少重复代码和类的数量。
- 例如,对于具有相似操作的命令,可以抽象出一个基类,让具体命令类继承该基类,复用公共的代码逻辑。
- 遵循设计原则
- 严格遵循单一职责原则,确保每个类和接口只负责单一的功能。例如,具体命令类只负责封装特定的操作,接收者类只负责执行具体的业务逻辑。
- 遵循开闭原则,在需要扩展新命令时,通过创建新的具体命令类来实现,而不是修改现有的代码。这样可以提高系统的可维护性和扩展性。
实现层面
- 内存管理
- 由于命令模式会创建大量的命令对象,要注意内存的使用情况。特别是在命令频繁执行和撤销的场景下,可能会产生大量的临时对象,导致内存占用过高。
- 可以考虑使用对象池技术来复用命令对象,减少对象的创建和销毁开销,提高系统的性能。
- 命令撤销和重做的实现
- 如果需要支持命令的撤销和重做功能,要确保在命令对象中正确记录执行前的状态。在实现
undo()
方法时,要保证能准确地恢复到执行命令之前的状态。 - 同时,要处理好撤销和重做操作的顺序和边界条件,避免出现状态不一致的问题。
- 如果需要支持命令的撤销和重做功能,要确保在命令对象中正确记录执行前的状态。在实现
- 线程安全
- 在多线程环境下使用命令模式时,要考虑线程安全问题。如果多个线程同时操作命令队列或命令对象,可能会导致数据不一致或其他并发问题。
- 可以通过同步机制(如
synchronized
关键字或Lock
接口)来保证线程安全,或者使用线程安全的数据结构来存储命令。
使用场景层面
- 适用场景判断
- 命令模式适用于需要将请求的发送者和接收者解耦、支持命令的撤销和重做、实现命令队列和日志记录等场景。在使用前要仔细分析系统需求,判断是否真正适合使用命令模式。
- 如果系统的功能简单,请求和执行逻辑紧密耦合,使用命令模式可能会增加系统的复杂度,得不偿失。
- 与其他模式结合使用
- 在实际开发中,命令模式可以与其他设计模式结合使用,以更好地满足系统需求。例如,与观察者模式结合,当命令执行完成后通知相关的观察者;与组合模式结合,实现命令的组合和嵌套执行。
- 但在结合使用时,要注意不同模式之间的协调和兼容性,避免引入新的问题。
应用场景
1. 图形用户界面(GUI)应用程序
- 按钮和菜单操作 :在各种桌面应用程序、Web 应用程序或移动应用程序的图形用户界面中,用户通过点击按钮、选择菜单项等操作来触发各种功能。每个操作都可以封装成一个命令对象。例如,在文本编辑器中,"保存""复制""粘贴" 等操作对应的按钮点击事件可以分别封装成
SaveCommand
、CopyCommand
、PasteCommand
等命令对象。这样,GUI 组件(如按钮)作为调用者,只需要调用命令对象的execute()
方法,而不需要关心具体的操作是如何实现的。 - 撤销和重做功能 :命令模式天然支持撤销和重做操作。在 GUI 应用中,这是一个非常常见且重要的功能。比如在图像编辑软件中,用户进行的每一步操作(如裁剪、调色、添加滤镜等)都可以封装成命令对象。当用户点击 "撤销" 按钮时,系统可以依次调用已执行命令对象的
undo()
方法,将图像恢复到之前的状态;点击 "重做" 按钮时,则可以再次调用这些命令对象的execute()
方法。
2. 游戏开发
- 游戏操作控制 :游戏中的角色移动、攻击、释放技能等操作都可以使用命令模式来实现。例如,在一个角色扮演游戏中,玩家按下键盘上的某个按键来控制角色向前移动,这个操作可以封装成
MoveForwardCommand
命令对象。游戏控制器作为调用者,根据玩家的输入调用相应命令对象的execute()
方法。这样可以方便地实现游戏操作的自定义和扩展,例如可以通过修改命令对象来改变角色的移动速度或攻击方式。 - 游戏回放和存档:命令模式可以记录游戏中的所有操作,这些操作记录可以用于游戏回放。同时,也可以将这些命令序列保存到文件中,实现游戏存档功能。当玩家加载存档时,系统可以依次执行这些命令,将游戏状态恢复到存档时的状态。
3. 工作流系统
- 任务调度和执行 :在工作流系统中,每个任务的执行可以看作是一个命令。例如,在一个项目管理系统中,任务的分配、审批、完成等操作都可以封装成不同的命令对象。工作流引擎作为调用者,根据工作流的规则依次调用这些命令对象的
execute()
方法,完成任务的调度和执行。 - 流程撤销和恢复:如果工作流中某个步骤出现错误或需要修改,命令模式的撤销和重做功能可以方便地实现流程的回退和恢复。例如,在一个审批流程中,如果审批人员误操作通过了一个不应该通过的申请,可以通过撤销相应的审批命令,将流程恢复到之前的状态。
4. 多线程和分布式系统
- 任务队列和异步处理:在多线程或分布式系统中,命令模式可以用于实现任务队列和异步处理。将需要执行的任务封装成命令对象,放入任务队列中,多个工作线程或分布式节点从队列中取出命令对象并执行。这样可以实现任务的解耦和异步执行,提高系统的并发性能和可扩展性。
- 日志记录和故障恢复:在分布式系统中,命令模式可以方便地记录每个操作的日志。当系统出现故障时,可以根据日志中的命令序列进行故障恢复。例如,在一个分布式数据库系统中,对数据库的增删改操作可以封装成命令对象,记录操作日志。当数据库节点出现故障重启时,可以根据日志中的命令对象重新执行操作,保证数据的一致性。
5. 智能家居系统
- 设备控制 :智能家居系统中,用户可以通过手机 APP 或智能遥控器来控制各种智能设备,如灯光、空调、窗帘等。每个设备的操作(如开灯、调节温度、打开窗帘等)都可以封装成命令对象。智能控制中心作为调用者,根据用户的指令调用相应命令对象的
execute()
方法,实现对设备的远程控制。 - 场景模式设置:智能家居系统通常支持场景模式,如 "回家模式""睡眠模式" 等。每个场景模式可以看作是一组命令的集合。例如,"回家模式" 可以包含打开灯光、打开空调、拉开窗帘等命令。当用户选择某个场景模式时,系统依次执行该场景模式下的所有命令对象。