Go用函数类型和结构体实现命令模式,核心是将请求封装为对象以支持排队、撤销等;不依赖继承,推荐struct+函数字段或闭包方式。

命令模式在 Go 里不靠接口继承或抽象类实现,而是用函数类型和结构体组合来封装“可执行动作”,核心是把请求变成对象,支持排队、撤销、日志等扩展。
为什么 Go 不适合照搬传统命令模式写法
Go 没有 class、abstract、override 等机制,强行模仿 Java/C# 的 Command 接口 + 多个实现类,反而导致冗余代码和类型膨胀。更自然的方式是:
- 用
func()或带参数的函数类型(如func(string) error)表示“执行逻辑” - 用结构体字段保存上下文(如目标对象、参数、回调)
- 把
Execute()、Undo()变成结构体方法,内部调用闭包或委托函数
用 struct + 函数字段封装命令(推荐做法)
这是最常用、易读、易测试的模式。每个命令实例持有自己的状态和行为,不依赖继承体系。
type FileCommand struct {
path string
action func(string) error
undo func(string) error
}
func (fc *FileCommand) Execute() error {
return fc.action(fc.path)
}
func (fc *FileCommand) Undo() error {
return fc.undo(fc.path)
}
// 使用示例
cmd := &FileCommand{
path: "/tmp/data.txt",
action: os.WriteFile,
undo: os.Remove,
}
err := cmd.Execute() // 写入文件
if err != nil {
log.Fatal(err)
}
// ... 后续可调用 cmd.Undo()
注意:os.WriteFile 和 os.Remove 类型需匹配——这里它们都接受 string 作为第一个参数,否则要包装一层适配器。
立即学习“go语言免费学习笔记(深入)”;
用闭包捕获上下文,避免暴露结构体字段
当命令逻辑简单、状态少,且不想定义新类型时,直接返回闭包更轻量。适合一次性操作或配置驱动场景。
func MakeDeleteCommand(path string) func() error {
return func() error {
return os.Remove(path)
}
}
func MakeCopyCommand(src, dst string) func() error {
return func() error {
data, _ := os.ReadFile(src)
return os.WriteFile(dst, data, 0644)
}
}
// 使用
delCmd := MakeDeleteCommand("/tmp/old.log")
err := delCmd() // 执行删除
copyCmd := MakeCopyCommand("/var/log/app.log", "/backup/app.log")
err = copyCmd() // 执行复制
这种写法无法统一支持 Undo(),除非额外返回一个逆操作闭包,例如:func() (func() error, error)。但会增加调用方理解成本。
命令队列与批量执行时要注意资源生命周期
如果把多个命令存进切片再统一执行(比如 CLI 工具的 --dry-run 模式),必须确保每个命令持有的资源(如文件句柄、HTTP client、DB 连接)没有被提前释放或复用冲突。
- 不要在命令闭包中引用局部变量地址(如
&v),循环中容易全部指向最后一个值 - 避免在命令中直接使用未拷贝的指针或 map/slice,除非明确知道其生命周期覆盖整个执行过程
- 若命令依赖外部服务(如数据库),建议把 client 作为字段传入结构体,而不是在闭包里捕获——便于单元测试 mock
真正容易被忽略的是:Go 的命令模式不是为了“设计模式达标”,而是为了解耦调用者与执行者、让操作可记录/可重放/可审计。别为了封装而封装,优先看是否真需要 Undo、Retry 或 LogBeforeExecute 这些能力。










