命令模式是一种行为型设计模式,它将请求封装成对象,从而允许用户使用不同的请求、队列或者请求日志来参数化其他对象,并且支持可撤销的操作,这种模式的核心思想是将“发出请求的对象”与“接收并执行请求的对象”解耦,使得请求本身可以像对象一样被传递、存储和管理,在《大话设计模式》一书中,命令模式通过生动的比喻和实际案例,让读者深入理解其设计原理和应用场景。

命令模式的结构与角色
命令模式通常包含以下四个核心角色:
- 命令角色(Command):声明执行操作的接口,通常包含一个执行方法(如execute)和一个撤销方法(如undo)。
- 具体命令角色(ConcreteCommand):实现命令接口,将一个接收者对象绑定到一个动作上,调用接收者相应的操作来执行请求。
- 调用者角色(Invoker):要求命令对象执行请求,它不知道具体的请求内容,只知道如何调用命令对象的execute方法。
- 接收者角色(Receiver):知道如何执行与请求相关的操作,是真正干活的对象,一个接收者可以处理多个不同的请求。
通过这种角色划分,调用者无需知道接收者的具体实现,只需通过命令对象间接调用接收者的方法,从而实现了调用者与接收者之间的解耦。
命令模式的工作流程
以《大话设计模式》中“遥控器控制电器”的案例为例,假设我们有一个遥控器,可以控制电灯的开关和风扇的启停,使用命令模式后,系统的工作流程如下:
- 创建接收者:定义电灯(Light)和风扇(Fan)等接收者类,它们包含具体的操作方法,如light.on()、light.off()、fan.start()、fan.stop()。
- 创建具体命令:定义LightOnCommand、LightOffCommand、FanStartCommand、FanStopCommand等具体命令类,每个命令类持有一个接收者对象,并在execute方法中调用接收者的相应方法。
- 创建调用者:定义遥控器(RemoteControl)类,它包含一组命令对象(如Command[] slots),并提供设置命令(setCommand)和执行命令(buttonWasPressed)的方法。
- 客户端组装:在客户端代码中,创建接收者、具体命令和调用者,并将命令对象绑定到调用者的插槽中,将LightOnCommand绑定到遥控器的第一个按钮,当按下按钮时,调用者会执行该命令的execute方法。
通过以上流程,遥控器(调用者)无需知道电灯或风扇的具体实现,只需通过命令对象即可控制电器,符合“开闭原则”——新增电器时只需新增对应的命令类,无需修改遥控器的代码。

命令模式的优缺点
优点:
- 解耦调用者与接收者:调用者只需知道命令接口,无需关心接收者的具体实现,降低了系统的耦合度。
- 支持撤销和重做:通过在命令类中添加undo方法,可以实现操作的撤销和重做功能。
- 支持日志和事务:命令对象可以被记录到日志中,支持事务操作(如执行一组命令后统一提交或回滚)。
- 支持宏命令:可以将多个命令组合成一个宏命令(CompositeCommand),一次性执行多个操作。
缺点:
- 增加系统复杂性:对于简单的请求,使用命令模式可能会增加不必要的类和对象数量。
- 可能产生大量具体命令类:如果系统中存在大量不同的请求,需要定义许多具体命令类,导致类数量膨胀。
命令模式的应用场景
- GUI按钮和菜单项:在图形用户界面中,每个按钮或菜单项都可以封装为一个命令对象,点击时触发相应的操作。
- 多线程任务调度:将任务封装为命令对象,放入线程池中执行,实现任务的异步处理。
- 撤销/重做功能:在文本编辑器、绘图软件等应用中,通过命令模式记录操作历史,支持撤销和重做。
- 宏命令:在批量操作中,将多个命令组合成一个宏命令,如“一键关闭所有电器”。
命令模式的扩展:宏命令与队列请求
命令模式还可以扩展为宏命令(MacroCommand),即一个命令对象包含多个子命令,当执行宏命令时,它会依次调用所有子命令的execute方法,一个“回家模式”的宏命令可能包含“打开电灯”“打开空调”“播放音乐”等多个子命令。
命令对象可以被放入队列中实现请求的排队处理,在任务管理系统中,多个任务被封装为命令对象并放入队列,系统按顺序执行这些任务。
相关问答FAQs
Q1: 命令模式与策略模式有什么区别?
A1: 命令模式与策略模式都是行为型设计模式,但它们解决的问题不同,命令模式的核心是“封装请求”,将请求的发送者和接收者解耦,支持撤销、重做和日志等功能;而策略模式的核心是“封装算法”,使算法可以独立于使用它的客户端而变化,主要用于替换或选择不同的算法,命令模式可能用于“撤销操作”,而策略模式可能用于“选择不同的排序算法”。
Q2: 如何在命令模式中实现撤销功能?
A2: 在命令模式中实现撤销功能,可以在命令接口中添加一个undo方法,具体命令类在execute方法中执行操作的同时,记录执行前的状态(如接收者的属性值),当调用undo方法时,命令类根据记录的状态恢复接收者的属性,LightOnCommand的execute方法调用light.on(),undo方法则调用light.off();对于更复杂的操作(如调节温度),可以在命令类中保存旧值,undo时恢复旧值,可以通过“备忘录模式”来保存接收者的完整状态,以支持更灵活的撤销操作。

