AIGC标识 命令模式(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 实现要点

  1. 命令接口通常只有两个方法:Execute(执行)和 Undo(撤销),保持接口最小化
  2. 函数也是一种命令:Go 中 func() 本身就是零参数零返回的"命令",用 func 类型可以省去结构体定义
  3. 撤销需要保存状态:Undo 方法中需要恢复执行前的状态,命令对象应在 Execute 时记录必要的快照
  4. 宏命令用切片组合:一个 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() 时保存了接收者执行前的状态快照(prevIsOnprevBright 等)。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 或组合,就用接口+结构体的完整实现。

posted @ 2026-08-06 09:23  FfHUCisI  阅读(4)  评论(0)    收藏  举报