Go中命令模式不用接口继承,而用func()类型和结构体组合,核心是将动作变为值;优先用type Command func(),需撤销或状态管理时才用结构体封装。

命令模式在 Go 中没有接口继承,得靠函数类型和结构体组合
Go 没有传统 OOP 的 Command 接口或抽象类,所以不能照搬 Java/C# 那套。核心思路是:用 func() 类型表示可执行行为,再用结构体封装上下文、参数和执行逻辑。关键不是“实现接口”,而是“把动作变成值”。
常见错误是强行定义 type Command interface { Execute() },然后每个命令都写一个 struct 实现它——这既没利用 Go 的简洁性,又失去闭包捕获状态的能力。
- 优先用
type Command func()定义类型别名,轻量且可直接调用 - 需要撤销、参数绑定或状态管理时,才用结构体字段存
execute和undo函数 - 避免为每个命令新建 struct;多数场景一个通用
SimpleCommand就够用
封装带上下文的命令:用结构体 + 函数字段最灵活
比如操作一个 FileWriter,需要打开文件、写内容、关闭。命令不仅要执行,还得知道对哪个文件操作——这时闭包或结构体字段都行,但结构体更易测试和复用。
type FileWriterCommand struct {
filename string
content string
writer *os.File
}
func (c *FileWriterCommand) Execute() error {
f, err := os.OpenFile(c.filename, os.O_CREATE|os.O_WRONLY|os.OAPPEND, 0644)
if err != nil {
return err
}
c.writer = f
, err = f.WriteString(c.content)
return err
}
func (c *FileWriterCommand) Undo() error {
if c.writer != nil {
return c.writer.Close()
}
return nil
}
注意:这里 Undo() 不回滚写入内容(那是业务逻辑),只负责资源清理。命令模式不保证语义上的“可逆”,只提供统一调用入口。
立即学习“go语言免费学习笔记(深入)”;
- 结构体字段应只存必要上下文,不要塞大对象(如整个 DB 连接池)
- 如果
Execute可能失败,返回error是必须的;调用方需检查 - 避免在
Execute()里做阻塞 IO 而不提供上下文(context.Context),否则无法超时控制
命令队列与批量执行:用 slice 存 func() 最简单
想按顺序执行一组操作?不需要专门的 Invoker 类。Go 里直接用 []func() error 就行:
commands := []func() error{
func() error { return saveUser(&user) },
func() error { return sendEmail(user.Email, "welcome") },
func() error { return logAction("user_created", user.ID) },
}
for i, cmd := range commands {
if err := cmd(); err != nil {
log.Printf("command %d failed: %v", i, err)
break // 或 continue,取决于容错策略
}
}
这种写法比抽象出 Invoker.ExecuteAll() 更符合 Go 的直觉。真正复杂的调度(如并发、重试、依赖顺序)该交给专用库(如 go-workers 或自定义 DAG 调度器),而不是硬塞进命令模式。
- 不要在命令 slice 里混入不同生命周期的对象(比如有些命令依赖 HTTP client,有些依赖 DB)
- 若需统一错误处理,可封装一层
RunCommands(commands []func() error) error - 命令本身不应共享可变状态;如有,用指针传参并加文档说明
容易被忽略的点:命令不是事务,也不自动管理生命周期
很多初学者以为封装成命令就等于“可回滚事务”。实际上:Execute() 和 Undo() 是完全独立的函数调用,Go 不会自动调用 Undo ——你得自己记下哪些执行成功了、哪些失败了、要不要回退。
另一个坑是资源泄漏:比如命令里打开了文件、数据库连接、HTTP 连接,但没在 Undo() 或 defer 中关闭。Go 没有析构函数,也不会帮你调 Close()。
- 命令执行失败后是否要调
Undo?这是业务决策,不是模式强制的 - 如果命令涉及外部系统(如发短信、调第三方 API),
Undo往往不可行,此时应明确标记该命令“不可撤销” - 结构体命令建议实现
String() string方法,方便日志和调试时识别当前命令










