命令模式(Command Pattern)
命令模式(Command Pattern)
一、模式定义
命令模式将一个请求封装为一个对象,从而允许你用不同的请求对调用者进行参数化、将请求排队、记录日志,以及支持撤销操作。
一句话理解:把"做事"变成"造东西"。 不是直接调用 light.TurnOn(),而是构造一个 TurnOnCommand{light} 对象,让调用者去执行它。这层封装带来了延迟执行、排队、撤销、日志等能力。
二、解决什么问题
- 调用者与接收者解耦:按钮不知道它控制的是灯还是空调,只管执行命令
- 撤销/重做:命令对象可以记录执行前的状态,支持 Undo
- 请求排队与调度:命令对象可以放入队列,由调度器按序执行
- 宏命令:一组命令打包成一个复合命令,一键执行多个操作
三、核心角色
| 角色 | 职责 |
|---|---|
| Command(命令接口) | 声明 Execute 和 Undo 方法 |
| ConcreteCommand | 持有接收者引用,实现具体操作 |
| Receiver(接收者) | 实际执行操作的对象 |
| Invoker(调用者) | 持有命令引用,触发执行 |
| Client(客户端) | 创建命令并绑定接收者 |
四、结构图
┌───────────────┐ ┌──────────────────┐ ┌───────────────┐
│ Invoker │ │ Command │ │ Receiver │
├───────────────┤ ├──────────────────┤ ├───────────────┤
│ - cmds []Cmd │────>│ + Execute() │────>│ + Action() │
│ + ExecuteCmd()│ │ + Undo() │ └───────────────┘
│ + UndoCmd() │ └────────┬─────────┘
└───────────────┘ │ implements
┌──────────┴──────────┐
│ │
┌──────┴──────┐ ┌───────┴───────┐
│LightOnCmd │ │MacroCommand │
│- light │ │- cmds []Cmd │
│+Execute() │ │+Execute() │
│+Undo() │ │+Undo() │
└─────────────┘ └───────────────┘
五、Go 实现要点
- 命令接口通常只有两个方法:Execute(执行)和 Undo(撤销),保持接口最小化
- 函数也是一种命令:Go 中
func()本身就是零参数零返回的"命令",用func类型可以省去结构体定义 - 撤销需要保存状态:Undo 方法中需要恢复执行前的状态,命令对象应在 Execute 时记录必要的快照
- 宏命令用切片组合:一个 MacroCommand 内部持有多个 Command,Execute 时依次执行,Undo 时逆序撤销
六、完整练习代码
以智能家居遥控器为场景:按钮控制灯、空调、窗帘,支持撤销和宏命令。
package main
import "fmt"
// ============================================================
// 命令接口
// ============================================================
// Command 命令接口
type Command interface {
Execute()
Undo()
Description() string
}
// ============================================================
// 接收者:各种家居设备
// ============================================================
// Light 灯
type Light struct {
name string
isOn bool
brightness int // 0-100
}
func NewLight(name string) *Light {
return &Light{name: name}
}
func (l *Light) TurnOn() {
l.isOn = true
l.brightness = 80
fmt.Printf(" [%s] 开灯,亮度 %d%%\n", l.name, l.brightness)
}
func (l *Light) TurnOff() {
l.isOn = false
fmt.Printf(" [%s] 关灯\n", l.name)
}
// AirConditioner 空调
type AirConditioner struct {
name string
isOn bool
temperature int
}
func NewAC(name string) *AirConditioner {
return &AirConditioner{name: name}
}
func (ac *AirConditioner) TurnOn() {
ac.isOn = true
ac.temperature = 26
fmt.Printf(" [%s] 开空调,温度 %d°C\n", ac.name, ac.temperature)
}
func (ac *AirConditioner) TurnOff() {
ac.isOn = false
fmt.Printf(" [%s] 关空调\n", ac.name)
}
// Curtain 窗帘
type Curtain struct {
name string
isOpen bool
}
func NewCurtain(name string) *Curtain {
return &Curtain{name: name, isOpen: true}
}
func (c *Curtain) Open() {
c.isOpen = true
fmt.Printf(" [%s] 开窗帘\n", c.name)
}
func (c *Curtain) Close() {
c.isOpen = false
fmt.Printf(" [%s] 关窗帘\n", c.name)
}
// ============================================================
// 具体命令实现
// ============================================================
// --- 灯开关命令 ---
type LightOnCommand struct {
light *Light
// 撤销快照
prevIsOn bool
prevBright int
}
func NewLightOnCommand(light *Light) *LightOnCommand {
return &LightOnCommand{light: light}
}
func (c *LightOnCommand) Execute() {
c.prevIsOn = c.light.isOn
c.prevBright = c.light.brightness
c.light.TurnOn()
}
func (c *LightOnCommand) Undo() {
if !c.prevIsOn {
c.light.TurnOff()
} else {
c.light.isOn = true
c.light.brightness = c.prevBright
fmt.Printf(" [%s] 恢复亮度 %d%%\n", c.light.name, c.prevBright)
}
}
func (c *LightOnCommand) Description() string {
return "开灯"
}
// --- 空调开关命令 ---
type ACOnCommand struct {
ac *AirConditioner
prevIsOn bool
prevTemp int
}
func NewACOnCommand(ac *AirConditioner) *ACOnCommand {
return &ACOnCommand{ac: ac}
}
func (c *ACOnCommand) Execute() {
c.prevIsOn = c.ac.isOn
c.prevTemp = c.ac.temperature
c.ac.TurnOn()
}
func (c *ACOnCommand) Undo() {
if !c.prevIsOn {
c.ac.TurnOff()
} else {
c.ac.isOn = true
c.ac.temperature = c.prevTemp
fmt.Printf(" [%s] 恢复温度 %d°C\n", c.ac.name, c.prevTemp)
}
}
func (c *ACOnCommand) Description() string {
return "开空调"
}
// --- 窗帘关闭命令 ---
type CurtainCloseCommand struct {
curtain *Curtain
prevOpen bool
}
func NewCurtainCloseCommand(curtain *Curtain) *CurtainCloseCommand {
return &CurtainCloseCommand{curtain: curtain}
}
func (c *CurtainCloseCommand) Execute() {
c.prevOpen = c.curtain.isOpen
c.curtain.Close()
}
func (c *CurtainCloseCommand) Undo() {
if c.prevOpen {
c.curtain.Open()
} else {
fmt.Printf(" [%s] 维持关闭\n", c.curtain.name)
}
}
func (c *CurtainCloseCommand) Description() string {
return "关窗帘"
}
// --- 宏命令:组合多个命令 ---
type MacroCommand struct {
name string
commands []Command
}
func NewMacroCommand(name string, commands ...Command) *MacroCommand {
return &MacroCommand{name: name, commands: commands}
}
func (m *MacroCommand) Execute() {
fmt.Printf(" >>> 执行宏命令: %s\n", m.name)
for _, cmd := range m.commands {
cmd.Execute()
}
}
func (m *MacroCommand) Undo() {
fmt.Printf(" <<< 撤销宏命令: %s\n", m.name)
// 逆序撤销
for i := len(m.commands) - 1; i >= 0; i-- {
m.commands[i].Undo()
}
}
func (m *MacroCommand) Description() string {
return m.name
}
// ============================================================
// 调用者:遥控器
// ============================================================
type RemoteControl struct {
history []Command // 命令历史,用于撤销
}
func NewRemoteControl() *RemoteControl {
return &RemoteControl{}
}
// PressButton 按下按钮执行命令
func (r *RemoteControl) PressButton(cmd Command) {
fmt.Printf("[遥控器] 执行: %s\n", cmd.Description())
cmd.Execute()
r.history = append(r.history, cmd)
}
// PressUndo 撤销上一次命令
func (r *RemoteControl) PressUndo() {
if len(r.history) == 0 {
fmt.Println("[遥控器] 没有可撤销的操作")
return
}
// 取出最后一条命令
lastIdx := len(r.history) - 1
cmd := r.history[lastIdx]
r.history = r.history[:lastIdx]
fmt.Printf("[遥控器] 撤销: %s\n", cmd.Description())
cmd.Undo()
}
// ============================================================
// 主函数
// ============================================================
func main() {
// 创建设备
livingLight := NewLight("客厅灯")
bedroomAC := NewAC("卧室空调")
livingCurtain := NewCurtain("客厅窗帘")
// 创建命令
lightOn := NewLightOnCommand(livingLight)
acOn := NewACOnCommand(bedroomAC)
curClose := NewCurtainCloseCommand(livingCurtain)
// 创建宏命令:"离家模式" = 关窗帘 + 开灯 + 开空调... 不对
// 离家模式应该是:关灯 + 关空调 + 关窗帘
// 但我们只有 On 命令,让我用宏命令演示"回家模式"
comeHome := NewMacroCommand("回家模式", lightOn, acOn, curClose)
// 创建遥控器
remote := NewRemoteControl()
fmt.Println("===== 1. 单个命令执行与撤销 =====")
remote.PressButton(lightOn)
remote.PressButton(acOn)
remote.PressUndo() // 撤销开空调
remote.PressUndo() // 撤销开灯
fmt.Println("\n===== 2. 宏命令一键执行 =====")
remote.PressButton(comeHome)
fmt.Println("\n===== 3. 宏命令整体撤销 =====")
remote.PressUndo() // 撤销整个回家模式
fmt.Println("\n===== 4. 撤销空历史 =====")
remote.PressUndo()
remote.PressUndo()
}
七、运行结果
===== 1. 单个命令执行与撤销 =====
[遥控器] 执行: 开灯
[客厅灯] 开灯,亮度 80%
[遥控器] 执行: 开空调
[卧室空调] 开空调,温度 26°C
[遥控器] 撤销: 开空调
[卧室空调] 关空调
[遥控器] 撤销: 开灯
[客厅灯] 关灯
===== 2. 宏命令一键执行 =====
[遥控器] 执行: 回家模式
>>> 执行宏命令: 回家模式
[客厅灯] 开灯,亮度 80%
[卧室空调] 开空调,温度 26°C
[客厅窗帘] 关窗帘
===== 3. 宏命令整体撤销 =====
[遥控器] 撤销: 回家模式
<<< 撤销宏命令: 回家模式
[客厅窗帘] 开窗帘
[卧室空调] 关空调
[客厅灯] 关灯
===== 4. 撤销空历史 =====
[遥控器] 没有可撤销的操作
[遥控器] 没有可撤销的操作
八、关键设计决策解析
1. Undo 的快照策略
命令对象在 Execute() 时保存了接收者执行前的状态快照(prevIsOn、prevBright 等)。Undo 时根据快照恢复。这是"命令模式支持撤销"的核心机制。
有两种快照策略:
- 局部快照(本实现):只保存变化的字段。优点是内存占用小,缺点是每个命令类都要手写快照逻辑
- 全量快照:在 Execute 前深拷贝整个接收者。优点是通用,缺点是内存浪费大
2. 宏命令的撤销顺序
宏命令 Execute 时正序执行(先A后B后C),Undo 时必须逆序撤销(先C后B后A)。这就像穿衣服和脱衣服——穿是先内后外,脱是先外后内,顺序不能乱。
3. 函数式命令的简化
当命令不需要 Undo 时,Go 可以用 func 类型大幅简化:
type SimpleCommand func()
func (f SimpleCommand) Execute() { f() }
但一旦需要 Undo,就必须用结构体保存状态,函数式方案就力不从心了。
4. 命令模式 vs 观察者模式
两者都涉及"触发操作",但方向不同:
- 命令模式:调用者 → 命令 → 接收者(一对一,明确知道发给谁)
- 观察者模式:被观察者 → 所有观察者(一对多,不关心具体是谁)
九、实际项目身影
database/sql事务:Begin → Exec → Commit/Rollback,Rollback 就是命令的 Undo- HTTP 中间件:每个中间件是一个命令,按链式顺序执行
- Git 操作:每个 commit 是一个命令,revert 就是 Undo
- 文本编辑器:每次编辑操作封装为命令,Ctrl+Z 就是 Undo
- 任务队列:把命令序列化后放入消息队列,Worker 取出后反序列化执行
十、小结
命令模式的本质是把"请求"从过程调用变成对象传递。这层封装看似多余,实则为延迟执行、排队、撤销、日志、宏操作提供了基础。在 Go 中,如果只需 Execute 不需 Undo,可以用 func 简化;一旦需要 Undo 或组合,就用接口+结构体的完整实现。

浙公网安备 33010602011771号