Go中命令模式不用接口继承,而用func()类型和结构体组合,核心是将动作变为值;优先用type Command func(),需撤销或状态管理时才用结构体封装。
Go 没有传统 OOP 的 Command 接口或抽象类,所以不能照搬 Java/C# 那套。核心思路是:用 func() 类型表示可执行行为,再用结构体封装上下文、参数和执行逻辑。关键不是“实现接口”,而是“把动作变成值”。
常见错误是强行定义 type Command interface { Execute() },然后每个命令都写一个 struct 实现它——这既没利用 Go 的简洁性,又失去闭包捕获状态的能力。
type Command func() 定义类型别名,轻量且可直接调用execute 和 undo 函数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() 不回滚写入内容(那是业务逻辑),只负责资源清理。命令模式不保证语义上的“可逆”,只提供统一调用入口。
Execute 可能失败,返回 error 是必须的;调用方需检查Execute() 里做阻塞 IO 而不提供上下文(context.Context),否则无法超时控制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 调度器),而不是硬塞进命令模式。
RunCommands(commands []func() error) error
很多初学者以为封装成命令就等于“可回滚事务”。实际上:Execute() 和 Undo() 是完全独立的函数调用,Go 不会自动调用 Undo ——你得自己记下哪些执行成功了、哪些失败了、要不要回退。
另一个坑是资源泄漏:比如命令里打开了文件、数据库连接、HTTP 连接,但没在 Undo() 或 defer 中关闭。Go 没有析构函数,也不会帮你调 Close()。
Undo?这是业务决策,不是模式强制的Undo 往往不可行,此时应明确标记该命令“不可撤销”String() string 方法,方便日志和调试时识别当前命令