命令模式¶
分类:行为型模式。来源:《Head First 设计模式》第 6 章。
类结构¶
classDiagram
direction TB
class Client
class Invoker {
+setCommand(Command command)
+invoke()
}
class Command {
<<interface>>
+execute()
}
class ConcreteCommand {
-Receiver receiver
+execute()
}
class Receiver {
+action()
}
Client ..> ConcreteCommand : 创建并装配
Client ..> Receiver : 配置接收者
Invoker --> Command : 触发执行
Command <|.. ConcreteCommand
ConcreteCommand --> Receiver : 调用业务操作
角色定义¶
| 角色 | 定义 |
|---|---|
| Client(客户) | 创建或选择接收者,构造具体命令,并把命令交给调用者。 |
| Invoker(调用者) | 保存命令引用,在合适时机调用 execute(),无需了解具体业务。 |
| Command(命令接口) | 声明统一的执行入口;需要撤销时可以进一步扩展相应接口。 |
| ConcreteCommand(具体命令) | 封装接收者及执行请求所需的数据,将执行操作委托给接收者。 |
| Receiver(接收者) | 提供真正完成业务工作的操作。 |
定义命令模式¶
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
参与对象¶
- 命令对象封装要执行的请求,并持有实际执行操作的接收者。
- 调用者通过统一的命令接口发起操作,不必知道接收者的具体实现。
- 接收者执行实际业务逻辑;具体命令决定调用哪些操作。
例如,遥控器按钮只调用命令的 execute(),开灯命令再调用电灯对象的 on()。需要撤销时,由命令保存必要状态并实现对应的撤销操作。
相关笔记¶
使用钩子¶
参见模板方法:使用钩子。
好莱坞原则¶
参见模板方法:好莱坞原则。
用模版方法排序¶
参见模板方法:用模板方法排序。
Head First 示例:支持撤销的遥控器¶
第 6 章用遥控器说明如何把“按按钮”与“设备怎样工作”分开。这里选用作者 2020 年第二版配套源码的 command/undo 版本,只画灯光控制,省略吊扇等其他设备。
这个版本的灯光命令会先保存执行前的亮度,撤销时调用 dim(level) 恢复;不是简单地把“开”改成“关”。遥控器只记录最近一次命令,图中不表示多级撤销历史。
| 示例类型 | 模式角色 | 职责 |
|---|---|---|
| RemoteControlWithUndo | 调用者 | 保存开、关命令数组及最近执行的命令,响应按钮操作。 |
| Command | 命令接口 | 声明 execute() 与 undo() 两个统一入口。 |
| LightOnCommand / LightOffCommand | 具体命令 | 保存灯和原亮度,执行开灯、关灯并支持恢复亮度。 |
| NoCommand | 空命令 | 为未配置的插槽提供不执行任何工作的命令对象。 |
| Light | 接收者 | 实际提供开、关、调光和读取当前亮度的操作。 |
示例 UML¶
classDiagram
direction TB
class RemoteControlWithUndo {
Command[] onCommands
Command[] offCommands
Command undoCommand
+setCommand(slot, onCommand, offCommand)
+onButtonWasPushed(slot)
+offButtonWasPushed(slot)
+undoButtonWasPushed()
}
class Command {
<<interface>>
+execute()
+undo()
}
class LightOnCommand {
int level
+execute()
+undo()
}
class LightOffCommand {
int level
+execute()
+undo()
}
class NoCommand {
+execute()
+undo()
}
class Light {
+on()
+off()
+dim(int level)
+getLevel() int
}
RemoteControlWithUndo --> Command : 插槽与最近命令
Command <|.. LightOnCommand
Command <|.. LightOffCommand
Command <|.. NoCommand
LightOnCommand --> Light : 控制并恢复亮度
LightOffCommand --> Light : 控制并恢复亮度
协作过程¶
RemoteLoader创建灯和开、关命令,再用setCommand()装入遥控器插槽。- 按下开灯按钮,遥控器调用开灯命令的
execute(),命令先读原亮度,再调用Light.on()。 - 遥控器把刚执行的命令赋给
undoCommand。 - 按撤销按钮时调用该命令的
undo(),命令用保存的亮度恢复灯的状态。
配套源码(2020 年第二版,固定版本):