Go中模板方法模式通过组合+接口+函数参数实现,核心是将可变逻辑抽为func或接口方法由调用方注入;轻量场景用函数参数,重逻辑用窄接口,横切关注用结构体钩子封装。

Go 没有继承,所谓“模板方法模式”只能靠组合 + 接口 + 函数参数模拟,核心不是复用父类骨架,而是把可变逻辑抽成 func 或接口方法,由调用方注入。
用函数参数替代抽象方法
把算法中变化的部分定义为函数类型,主流程固定,变化点作为参数传入。这是最轻量、最符合 Go 习惯的做法。
-
templateMethod不依赖任何结构体,只依赖输入参数和回调函数 - 避免为单次定制逻辑定义新类型或实现接口
- 适合逻辑差异小、生命周期短的业务场景(如不同渠道的订单校验)
type OrderValidator func(*Order) error
func ProcessOrder(order *Order, validate OrderValidator) error {
if err := validate(order); err != nil {
return err
}
if err := order.Charge(); err != nil {
return err
}
return order.SendNotification()
}
用接口约束行为差异
当变化逻辑较重、需要复用状态或多次调用时,定义接口比裸函数更清晰。注意:接口应窄——只包含该模板真正需要的方法。
- 接口名建议带
Processor、Strategy等后缀,避免泛称Handler - 不要让接口暴露无关方法(比如
Save或Log),否则违反里氏替换 - 实现类型内部可封装私有字段(如配置、客户端),但模板函数不感知
type OrderProcessor interface {
Validate(*Order) error
Charge(*Order) error
}
func RunOrderFlow(p OrderProcessor, order *Order) error {
if err := p.Validate(order); err != nil {
return err
}
return p.Charge(order)
}
组合结构体封装公共流程与钩子
当多个模板流程共享初始化、清理、日志、重试等横切逻辑时,用结构体组合比重复写函数更可控。关键在明确“钩子”的调用时机和默认行为。
立即学习“go语言免费学习笔记(深入)”;
- 钩子字段(如
Before,After)类型统一为func() error,便于零值安全调用 - 主流程方法(如
Execute)不暴露内部状态,只接收必要参数 - 避免在结构体里存业务数据;状态应由调用方传入,保持无副作用
type OrderTemplate struct {
Before func() error
After func() error
}
func (t *OrderTemplate) Execute(order *Order, process func(*Order) error) error {
if t.Before != nil {
if err := t.Before(); err != nil {
return err
}
}
err := process(order)
if t.After != nil {
t.After() // 忽略 after 错误,避免掩盖主流程错误
}
return err
}
真正难的不是写模板,而是判断哪部分该抽象、哪部分该固化。业务规则常变,但数据流向和错误处理路径往往稳定;优先抽象的是“做什么”,而不是“怎么做”。










