Go语言命令模式用函数类型或结构体实现:func()适合无状态操作,结构体支持Undo等复杂逻辑;需分离命令创建与执行,警惕闭包变量复用和值拷贝陷阱。

Go 语言没有类继承和接口强制实现,命令模式不能照搬 Java 那套写法;它更适合用函数类型、结构体组合和闭包来轻量实现——核心是把「请求」封装成可传递、可存储、可延迟执行的 Command 值。
用函数类型定义 Command 最简洁
Go 中最自然的命令抽象就是 func()。不需要额外接口或基类,直接把操作逻辑封装成无参无返回值的函数即可被统一调度。
- 适合一次性、无状态的操作(如日志记录、通知触发)
- 避免冗余结构体定义,降低认知负担
- 注意:若需撤销(
Undo),纯函数类型无法自带反向逻辑,必须搭配闭包捕获上下文
示例:
type Command func()
func NewSaveCommand(repo *Repository, data interface{}) Command {
return func() {
repo.Save(data)
}
}
// 使用
cmd := NewSaveCommand(userRepo, user)
cmd() // 执行
需要 Undo 或状态管理时,用结构体 + 方法实现
当命令要支持撤销、重做、参数检查或执行前校验时,结构体方式更可控。关键不是“实现某个接口”,而是确保结构体同时拥有 Execute() 和 Undo() 方法。
立即学习“go语言免费学习笔记(深入)”;
- 字段应只保存执行所需最小上下文(如 ID、旧值、依赖对象指针)
- 避免在
Execute()中做副作用以外的事(比如发起 HTTP 请求后又修改本地缓存) - 撤销操作不是“反向执行”,而是恢复到执行前一致状态;例如保存前先读取旧值,
Undo时写回
示例:
type UpdateUserCommand struct {
repo *UserRepository
userID string
newName string
oldName string // 执行前捕获,用于 Undo
}
func (c *UpdateUserCommand) Execute() {
c.oldName = c.repo.GetName(c.userID)
c.repo.UpdateName(c.userID, c.newName)
}
func (c *UpdateUserCommand) Undo() {
c.repo.UpdateName(c.userID, c.oldName)
}
命令队列与执行器要分离关注点
别把命令创建、排队、执行、错误处理全塞进一个地方。典型错误是让 CommandExecutor 直接 new 命令实例——这会导致耦合、难测试、无法复用命令。
- 命令由业务层构造并传入执行器(依赖注入)
- 执行器只负责调用
Execute(),记录失败、支持批量/串行/超时控制 - 如果要支持事务语义(全部成功或全部回滚),需在执行器中维护已成功命令列表,失败时逆序调用
Undo()
示例执行逻辑片段:
func (e *CommandExecutor) ExecuteAll(cmds []Command) error {
for _, cmd := range cmds {
if err := e.executeOne(cmd); err != nil {
return err
}
}
return nil
}
小心 Go 的值拷贝与闭包变量陷阱
命令常在循环中批量创建,容易因变量复用导致所有命令实际引用同一个值。这是 Go 中最隐蔽也最高频的命令模式 bug。
- 错误写法:
for _, id := range ids { cmds = append(cmds, func(){ do(id) }) }—— 所有闭包共享最后一个id - 正确写法:显式传参或用局部变量绑定,例如
for _, id := range ids { id := id; cmds = append(cmds, func(){ do(id) }) } - 结构体命令同理:循环中用
&UpdateUserCommand{...}创建指针,但确保字段值是独立拷贝而非引用原切片元素
这点不注意,命令看起来都“注册”了,执行时却全作用在同一个对象上。










