模板方法模式在Go中通过函数字段或接口实现,核心是控制反转:结构体定义固定流程,用户注入钩子逻辑;函数字段轻量适合简单场景,接口更易测试和共享状态,需注意错误处理、资源清理与并发安全。
Go 没有抽象类或 abstract 方法,所以不能像 Java 那样直接定义“子类必须重写”的钩子。但你可以用组合 + 接口 + 函数字段模拟出等效行为:把算法骨架封装在结构体里,把可变步骤暴露为 func() 或接口方法。
关键不是“继承复用”,而是“控制反转”——主流程由模板结构体控制,细节由使用者注入。
Execute())before, process, after func())或嵌入接口(如 Processor)让调用方提供定制逻辑这是最轻量、最符合 Go 习惯的做法。适合流程简单、变化点少(通常 ≤3 个)的场景。
type DataProcessor struct {
before func()
process func() error
after func()
}
func (dp *DataProcessor) Execute() error {
if dp.before != nil {
dp.before()
}
if dp.process == nil {
return fmt.Errorf("process function not set")
}
if err := dp.process(); err != nil {
return err
}
if dp.after != nil {
dp.after()
}
return nil
}
使用时直接赋值函数:
proc := &DataProcessor{
before: func() { log.Println("starting...") },
process: func() error {
return os.WriteFile("output.txt", []byte("hello"), 0644)
},
after: func() { log.Println("done.") },
}
err := proc.Execute()
nil ——尤其 process 这类必填项,漏设会导致运行时报错当流程变复杂、需要 mock、或者多个步骤之间要共享状态(如中间结果缓存)时,接口更合适。定义一个最小接口,让使用者实现它:
type Processor interface {
Setup() error
Run() error
Cleanup() error
}
type TemplateRunner struct {
p Processor
}
func (tr *TemplateRunner) Execute() error {
if err := tr.p.Setup(); err != nil {
return err
}
if err := tr.p.Run(); err != nil {
return err
}
return tr.p.Cleanup()
}
实现示例:
type FileJob struct {
InputPath string
OutputPath string
Content []byte
}
func (f *FileJob) Setup() error {
if _, err := os.Stat(f.InputPath); os.IsNotExist(err) {
return fmt.Errorf("input file missing: %s", f.InputPath)
}
return nil
}
func (f *FileJob) Run() error {
return os.WriteFile(f.OutputPath, f.Content, 0644)
}
func (f *FileJob) Cleanup() error {
log.Printf("wrote %d bytes to %s", len(f.Content), f.OutputPath)
return nil
}
// 使用
runner := &TemplateRunner{p: &FileJob{
InputPath: "config.yaml",
OutputPath: "result.json",
Content: []byte(`{"status":"ok"}`),
}}
runner.Execute()
FileJob 的 Content),避免重复参数传递模板方法看似简单,但在真实项目中容易因忽略执行时机或错误处理导致静默失败或资源泄漏。

defer 在模板方法内部使用要谨慎:如果 Setup() 失败,defer Cleanup() 不会触发;应改用显式 if err != nil { p.Cleanup() }
error 都该被检查并按需中断流程,不要只写 _ = p.Run()
Cleanup() 或 after 真正释放资源——Go 不会自动帮你关文件描述符TemplateRunner 实例,且其内部状态(如计数器、缓存 map)被修改,需加锁真正难的不是写骨架,而是想清楚哪些步骤必须串行、哪些可选、错误发生时要不要回滚、以及谁负责清理。这些决策不会出现在代码结构里,但会决定系统是否健壮。