主页 > IT业界  > 

设计模式之命令模式

设计模式之命令模式
概念

命令模式是一种行为型设计模式,它将请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。

定义

命令模式把一个请求或者操作封装到一个对象中,将发出命令的责任和执行命令的责任分割开。这样,命令的发送者不需要知道命令的接收者是谁,也不需要知道命令是如何被执行的,只需要关心如何发出命令。而命令的接收者只需要专注于如何执行命令,实现具体的业务逻辑。

想象一下,此时你手里握着一个智能家居的遥控器,可以控制家里的各种设备(灯、空调、音响)。最初,遥控器的每个按钮直接绑定了设备的操作——比如“开灯”按钮直接硬编码调用了客厅灯.turnOn() 的方法。这种方法看似简单,但是当设备升级的时候(比如换用语音控制的智能灯),你就需要拆开遥控器重新焊接电路;想新增一个“观影模式”(开灯、开空调、关窗帘)时,你又需要继续再遥控器内新增一堆新逻辑。而命令模式(Command) 则是把每一个操作(如开灯、调节温度)封装成独立的 命令对象,例如LightOnCommand包含执行(execute())和撤销(undo())方法,内部持有对灯具的引用。遥控器(调用者)完全无需知道设备细节,只需存储并触发这些命令对象。当你按下“开灯”按钮时,遥控器只是调用lightCommand.execute(),具体是传统灯具还是智能灯执行,它毫不关心。

那么,在这样的设计模式下,就会体现出下述三个优点:

灵活扩展:新增空调控制,只需创建一个AirconCommand丢给遥控器,无需改动原有代码;支持宏命令:将关灯、开空调、降窗帘组合成MovieModeCommand,一键触发复杂操作;实现撤销功能:执行命令后,遥控器记录历史,按下“撤销”键即可回退到上一步状态。

这就是命令模式的核心——将“请求”抽象为对象,让调用者和接收者解耦。

组成部分 命令接口(Command):声明了执行命令的抽象方法,所有具体命令类都需要实现这个接口。它定义了一个统一的执行命令的方法,通常命名为execute等,用来规范具体命令类的行为。具体命令类(ConcreteCommand):实现了命令接口,持有一个接收者对象的引用,在execute方法中调用接收者的相关方法来完成具体的操作。它将命令的执行和接收者的具体行为绑定在一起,实现了命令的具体逻辑。接收者(Receiver):知道如何执行与请求相关的操作,具体命令对象会调用它的方法来完成命令的执行。它是真正执行命令的对象,负责实现命令所对应的具体业务逻辑。调用者(Invoker):负责调用命令对象的execute方法来发起命令。它不直接与接收者交互,而是通过命令对象来间接执行操作,它可以设置命令对象,并在需要的时候触发命令的执行。客户端(Client):创建具体命令对象,并设置命令对象的接收者。在客户端中,将命令的发送者和接收者进行解耦,使得发送者不需要了解接收者的具体实现,只需要关心命令的发送。

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

我么使用智能家居遥控器的例子来编写示例代码。

类图

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实现 // 命令接口 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()方法,实现对设备的远程控制。场景模式设置:智能家居系统通常支持场景模式,如 “回家模式”“睡眠模式” 等。每个场景模式可以看作是一组命令的集合。例如,“回家模式” 可以包含打开灯光、打开空调、拉开窗帘等命令。当用户选择某个场景模式时,系统依次执行该场景模式下的所有命令对象。
标签:

设计模式之命令模式由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“设计模式之命令模式