命令模式在Go中核心是封装可撤销的执行单元,应使用结构体承载状态和上下文,明确依赖与错误处理,避免硬套接口,按需实现Undo而非强制统一。
Go 语言没有传统面向对象的抽象类或虚函数,所以硬套 UML 类图里的 Command 接口 + execute()/undo() 方法容易跑偏。真正关键的是:把「一个操作」封装成能延迟调用、能携带上下文、能统一管理生命周期的值。Go 里最自然的载体就是函数类型和结构体组合。
纯函数(如 func())无法自带状态,而真实命令往往需要参数、依赖、甚至回滚所需的数据快照。用结构体承载命令逻辑,既清晰又可控。
Execute() 和 Undo() 方法必须接收明确的上下文(比如 *App 或 DB),不能隐式依赖全局变量Execute() 应返回 error;Undo() 同理,且不应 panictype DeleteUserCommand struct {
UserID int
Name string // 执行前缓存,用于 undo 恢复
DB *sql.DB
}
func (c *DeleteUserCommand) Execute() error {
_, err := c.DB.Exec("DELETE FROM users WHERE id = ?", c.UserID)
return err
}
func 
(c *DeleteUserCommand) Undo() error {
_, err := c.DB.Exec("INSERT INTO users(id, name) VALUES (?, ?)", c.UserID, c.Name)
return err
}
典型误区是把命令堆进 slice 然后逆序调 Undo() —— 这只适用于线性、无分支的操作流。实际中更常见的是:用户执行 A → B → C,然后撤销 C,再执行 D,此时历史不该丢弃 A/B,但也不能让 D 的 undo 插在 C 前面。
[]Command)只记录已执行且**未被覆盖**的命令Undo(),而非遍历整个历史很多业务场景(如发通知、写日志、调第三方 API)根本不可逆。强行设计 Undo() 会引入额外状态管理成本,还可能掩盖真正的问题。
Undo() 可返回 errors.New("not supported"),调用方需处理该错误Execute() 是否按预期修改了状态,而不是纠结 undo 是否“完美”命令模式在 Go 里不是炫技工具,它是当你需要精确控制操作生命周期、支持重做/撤销、或解耦触发与执行时机时的务实选择。别先定义接口,先想清楚:这个操作要传什么?在哪执行?失败了怎么收场?undo 是刚需,还是自我感动?