Golang - 命令模式(Command Pattern)
2026/8/7 11:37:02 网站建设 项目流程

命令模式(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 时逆序撤销

六、完整练习代码

智能家居遥控器为场景:按钮控制灯、空调、窗帘,支持撤销和宏命令。

packagemainimport"fmt"// ============================================================// 命令接口// ============================================================// Command 命令接口typeCommandinterface{Execute()Undo()Description()string}// ============================================================// 接收者:各种家居设备// ============================================================// Light 灯typeLightstruct{namestringisOnboolbrightnessint// 0-100}funcNewLight(namestring)*Light{return&Light{name:name}}func(l*Light)TurnOn(){l.isOn=truel.brightness=80fmt.Printf(" [%s] 开灯,亮度 %d%%\n",l.name,l.brightness)}func(l*Light)TurnOff(){l.isOn=falsefmt.Printf(" [%s] 关灯\n",l.name)}// AirConditioner 空调typeAirConditionerstruct{namestringisOnbooltemperatureint}funcNewAC(namestring)*AirConditioner{return&AirConditioner{name:name}}func(ac*AirConditioner)TurnOn(){ac.isOn=trueac.temperature=26fmt.Printf(" [%s] 开空调,温度 %d°C\n",ac.name,ac.temperature)}func(ac*AirConditioner)TurnOff(){ac.isOn=falsefmt.Printf(" [%s] 关空调\n",ac.name)}// Curtain 窗帘typeCurtainstruct{namestringisOpenbool}funcNewCurtain(namestring)*Curtain{return&Curtain{name:name,isOpen:true}}func(c*Curtain)Open(){c.isOpen=truefmt.Printf(" [%s] 开窗帘\n",c.name)}func(c*Curtain)Close(){c.isOpen=falsefmt.Printf(" [%s] 关窗帘\n",c.name)}// ============================================================// 具体命令实现// ============================================================// --- 灯开关命令 ---typeLightOnCommandstruct{light*Light// 撤销快照prevIsOnboolprevBrightint}funcNewLightOnCommand(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=truec.light.brightness=c.prevBright fmt.Printf(" [%s] 恢复亮度 %d%%\n",c.light.name,c.prevBright)}}func(c*LightOnCommand)Description()string{return"开灯"}// --- 空调开关命令 ---typeACOnCommandstruct{ac*AirConditioner prevIsOnboolprevTempint}funcNewACOnCommand(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=truec.ac.temperature=c.prevTemp fmt.Printf(" [%s] 恢复温度 %d°C\n",c.ac.name,c.prevTemp)}}func(c*ACOnCommand)Description()string{return"开空调"}// --- 窗帘关闭命令 ---typeCurtainCloseCommandstruct{curtain*Curtain prevOpenbool}funcNewCurtainCloseCommand(curtain*Curtain)*CurtainCloseCommand{return&CurtainCloseCommand{curtain:curtain}}func(c*CurtainCloseCommand)Execute(){c.prevOpen=c.curtain.isOpen c.curtain.Close()}func(c*CurtainCloseCommand)Undo(){ifc.prevOpen{c.curtain.Open()}else{fmt.Printf(" [%s] 维持关闭\n",c.curtain.name)}}func(c*CurtainCloseCommand)Description()string{return"关窗帘"}// --- 宏命令:组合多个命令 ---typeMacroCommandstruct{namestringcommands[]Command}funcNewMacroCommand(namestring,commands...Command)*MacroCommand{return&MacroCommand{name:name,commands:commands}}func(m*MacroCommand)Execute(){fmt.Printf(" >>> 执行宏命令: %s\n",m.name)for_,cmd:=rangem.commands{cmd.Execute()}}func(m*MacroCommand)Undo(){fmt.Printf(" <<< 撤销宏命令: %s\n",m.name)// 逆序撤销fori:=len(m.commands)-1;i>=0;i--{m.commands[i].Undo()}}func(m*MacroCommand)Description()string{returnm.name}// ============================================================// 调用者:遥控器// ============================================================typeRemoteControlstruct{history[]Command// 命令历史,用于撤销}funcNewRemoteControl()*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(){iflen(r.history)==0{fmt.Println("[遥控器] 没有可撤销的操作")return}// 取出最后一条命令lastIdx:=len(r.history)-1cmd:=r.history[lastIdx]r.history=r.history[:lastIdx]fmt.Printf("[遥控器] 撤销: %s\n",cmd.Description())cmd.Undo()}// ============================================================// 主函数// ============================================================funcmain(){// 创建设备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类型大幅简化:

typeSimpleCommandfunc()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 或组合,就用接口+结构体的完整实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询