Go中可用嵌入+接口+组合实现模板方法模式:骨架结构体定义流程(如Run),可变逻辑抽为接口字段(如processor Processor),调用方注入具体实现,确保流程控制权在骨架侧、扩展点由使用者决定。

Go 里没有继承,怎么写模板方法
Go 不支持类继承,template method pattern 的经典实现(抽象基类定义流程骨架、子类覆写钩子)没法直接搬。但用「嵌入 + 接口 + 组合」能等效实现:把算法骨架放在结构体里,把可变部分抽成接口方法,让使用者传入具体实现。
关键不是“模拟 Java 风格”,而是守住两点:流程控制权在骨架侧、可扩展点由调用方决定。
常见错误是把所有逻辑塞进嵌入结构体,结果导致子类型无法干预中间步骤——这其实退化成了普通函数复用,不是模板方法。
- 骨架结构体只负责
Run、Validate、Process这类固定顺序的调用,不实现具体业务逻辑 - 每个可变环节必须声明为接口字段,比如
validator Validator、processor Processor - 避免在骨架中直接调用未导出方法或未声明接口的方法,否则嵌入后无法替换
嵌入结构体时如何避免方法覆盖冲突
嵌入是 Go 复用逻辑的核心手段,但容易踩坑:两个嵌入结构体有同名方法,编译报错 ambiguous selector;或者嵌入后意外覆盖了外层结构体本该有的行为。
立即学习“go语言免费学习笔记(深入)”;
这不是设计缺陷,是语言强制你显式处理歧义。实际项目里,90% 的冲突源于没提前约定方法命名边界或没做封装隔离。
- 给嵌入结构体的方法加前缀,比如
tmplValidate、tmplProcess,和业务方法区分开 - 如果嵌入的结构体本身带状态(如
cache map[string]string),别直接暴露它的字段,用GetCache()/SetCache(...)封装一层 - 不要嵌入多个含同名方法的结构体,宁可拆成字段 + 显式调用,比如
validator: &MyValidator{}而非MyValidator
为什么不用 embed 包而选结构体嵌入
embed 是用于静态文件的,和运行时逻辑复用无关。这里说的「嵌入结构体」指的是 type Base struct{ CommonLogic } 这种语法,不是 //go:embed。
新手常混淆这两个概念,搜 “Go embed template method” 结果跑偏。真正起作用的是结构体匿名字段机制,它提供字段和方法的自动提升,同时保留组合的透明性。
-
embed只能嵌入文件或文件夹,不能嵌入类型或方法 - 结构体嵌入才能让
base.Run()正常调用到CommonLogic.Validate() - 嵌入结构体后,仍可通过
base.CommonLogic访问底层实例,方便调试和替换
一个最小可运行的模板方法示例
下面这个例子没用任何第三方库,只依赖标准语法,重点看骨架怎么留钩子、子类型怎么插入手动逻辑:
type Processor interface {
Do(input string) string
}
type Workflow struct {
processor Processor
}
func (w *Workflow) Run(input string) string {
if input == "" {
return "empty"
}
return w.processor.Do(input) + "-done"
}
type UpperProcessor struct{}
func (u UpperProcessor) Do(input string) string {
return strings.ToUpper(input)
}
// 使用:
w := &Workflow{processor: UpperProcessor{}}
fmt.Println(w.Run("hello")) // 输出 "HELLO-done"
注意:这里 Workflow 不知道也不关心 UpperProcessor 怎么实现 Do,但它严格控制执行时机和前后逻辑。这才是模板方法的本质——流程守门员,不是逻辑搬运工。
最容易被忽略的一点:骨架结构体的字段必须是接口类型,而不是具体类型。写成 processor *UpperProcessor 就锁死了扩展能力,后续加 LowerProcessor 就得改骨架代码。










