大家好,我是十三!欢迎来到十三Tech。
前阵子做数据导入——三种来源(CSV、Excel、JSON)共用一套流程:读文件 → 解析 → 校验 → 写库 → 通知。最初三套流程各自写了一遍,五步顺序完全一样,只是每步的实现不同。改流程顺序时(比如加个"预处理"步骤)要改三处,经常漏改。
后来抽了一个 Pipeline 模板,把"读文件 → 解析 → 校验 → 写库 → 通知"的顺序写死在 Run 方法里,每步作为函数字段由调用方填充。三种来源各填一份,流程顺序只有一份代码。
这就是模板方法——算法骨架固定,步骤可替换。但教科书讲模板方法都假设有继承,Go 没继承怎么办?这一篇就聊聊 Go 里用函数字段实现模板方法的写法,以及它和策略的微妙差别。
一、模板方法在解决什么——不是"封装流程"
教科书定义:在父类定义算法骨架,把可变步骤延迟到子类实现。这话在 Java 时代有意义——继承是天然的扩展机制。但在 Go 里没有继承这回事,模板方法还有价值吗?
模板方法的真实价值是骨架与步骤分离——把"算法的固定结构"和"步骤的具体实现"拆成两份代码。这个价值跟语言无关。Go 里用组合+接口或函数字段,照样能实现等效语义。
判断口诀:流程顺序固定 + 步骤实现多变 → 模板方法;整个算法都可替换 → 策略。
模板方法和策略最容易混——都是"算法可变"。但粒度不同:模板方法替换的是算法的某个步骤,策略替换的是整个算法。数据导入流程换一种解析器是模板方法(其他步骤不变),换整个数据处理策略(从批处理改成流处理)是策略。
二、Go 里模板方法怎么写——函数字段最地道
Go 没有继承,模拟继承(嵌入 + 重写)能写但显得啰嗦。Go 社区更习惯用函数字段实现模板方法。
type Pipeline struct {
Load func() ([]byte, error)
Parse func([]byte) ([]Record, error)
Validate func([]Record) error
Save func([]Record) error
Notify func(int)
}
func (p *Pipeline) Run() error {
data, err := p.Load()
if err != nil { return err }
records, err := p.Parse(data)
if err != nil { return err }
if err := p.Validate(records); err != nil { return err }
if err := p.Save(records); err != nil { return err }
if p.Notify != nil { p.Notify(len(records)) }
return nil
}
看 Run 方法——它定义了五步固定顺序,每步的具体实现由调用方在装配时填充。Notify 是 nil-check 的——这是钩子(Hook),可选步骤,调用方想用就传、不想用就留 nil。强制步骤用接口、可选步骤用 nil-check 函数,这是 Go 里写模板方法的惯用风格。
写模板方法有个工程坑:流程顺序变更要谨慎。一旦骨架发布,调用方就依赖这个顺序。如果某天要加"预处理"步骤在 Load 之前,所有调用方的 Pipeline 装配都不用改(只是 Run 内部多一步),但如果要把"Save"提前到"Validate"之前,整个语义就变了——这种顺序变更要慎重。
三、什么时候别用模板方法
模板方法不是所有"流程化"场景都适用,至少有三个反模式。
反模式一:流程本身会变。如果流程顺序频繁变更(今天先校验后解析、明天先解析后校验),模板方法的固定骨架反而成了枷锁。这种场景用策略——把整个流程当作可替换的算法。
反模式二:步骤之间强耦合。模板方法假设步骤独立——Parse 不依赖 Validate 的结果、Save 不依赖 Notify 的执行。如果步骤之间有数据依赖或时序依赖,模板方法的简洁骨架掩盖了真实复杂度,调试时痛苦。
反模式三:子类/装配只有一两个。如果只有一种数据来源(比如只导入 CSV),直接写一个 ImportCSV() 函数就完了——五个步骤按顺序调。模板方法的成本(Pipeline 类型定义、装配代码)只有多场景才摊得平。
Go 标准库里模板方法最典型的地方是 net/http.Handler.ServeHTTP——http.Server 定义了请求处理的骨架(读请求 → 路由 → 调 handler → 写响应),具体的"怎么响应"由你实现的 Handler.ServeHTTP 填补。整个 HTTP 协议解析、连接管理、keep-alive 都由 Server 这个"模板"接管,你只需要实现"业务响应"这一个步骤。看 http.FileServer、http.RedirectHandler 这些标准实现,全都是模板方法的具体"子类"。
回头看那个数据导入——值得用模板方法吗?值得,三种来源、五步流程固定、各步实现差异大,正是模板方法的甜区。但如果只导入一种来源,直接写函数更清晰。
模板方法真正教会我的,不是"骨架与步骤分离",而是把"稳定的部分"和"易变的部分"分开——稳定的写成骨架,易变的做成接口或函数字段。这个分离原则比模式本身重要得多。
下一篇讲状态模式,它和策略长得最像但意图完全不同。
关于十三Tech 资深服务端研发,AI 实践者,专注分享真实可落地的技术经验。 相信 AI 是程序员的最佳搭档。
联系方式:569893882@qq.com GitHub:@TriTechAI
