Golang如何应用模板方法模式_Golang模板方法模式实践

12次阅读

go中模板方法用接口定义步骤契约+普通函数封装流程,如Processor接口与Run函数;钩子函数作字段需判空;所有钩子须支持context.Context;禁用embed模拟继承,避免反射与自动注册。

Golang如何应用模板方法模式_Golang模板方法模式实践

Go 里没有抽象类和继承,所以不能照搬 java 的模板方法写法;真正管用的,是用接口定义步骤契约 + 普通函数封装固定流程。

Interface + Run 函数实现最简骨架

这是最常用、最符合 Go 习惯的做法:不模拟“父类”,只明确“哪些步骤必须做、按什么顺序做”。

  • 定义一个 Processor 接口,每个方法对应一个可变步骤(如 Validate()Persist()
  • 写一个普通函数 Run(p Processor) Error,按序调用这些方法
  • 具体业务结构体实现该接口,内部可自由持有 *sql.DBcontext.Context 等共享状态
  • 别在接口里塞太多方法——5 个以上就该拆,比如把通知逻辑单独抽成 Notifier 接口
type Processor interface {     Validate() error     transform() error     Persist() error } func Run(p Processor) error {     if err := p.Validate(); err != nil {         return err     }     if err := p.Transform(); err != nil {         return err     }     return p.Persist() }

用结构体字段存钩子函数,适合轻量或临时定制

当只有 1–3 个可变点,且不想定义新类型时,直接把函数设为结构体字段更直观。

  • 字段名要体现语义和强制性,比如 Validate func() error 是必填,Notify func() error 可为 nil
  • Execute() 方法里显式判空,避免静默跳过关键步骤
  • 测试时直接传入闭包,不用构造完整 Struct,例如 pp.Validate = func() error { return nil }
  • 错误:别在 NewXXX() 里偷偷给字段赋默认函数,这会让调用方误以为流程已完备
type PaymentProcessor struct {     Validate func() error     Charge   func() error     Notify   func() error } func (p *PaymentProcessor) Execute() error {     if p.Validate == nil {         return fmt.Errorf("Validate not set")     }     if err := p.Validate(); err != nil {         return err     }     // ... }

所有钩子必须接收 context.Context 并支持中断

真实业务中,一个慢钩子(比如发微信通知)会拖垮整个流程;不加上下文管控,等于放弃超时和取消能力。

立即学习go语言免费学习笔记(深入)”;

  • 钩子方法签名统一为 func(ctx context.Context) error,主流程在每次调用前传入同一份 ctx
  • 若某步需限流,用 ctx, cancel := context.WithTimeout(ctx, 3*time.Second) 包一层再传
  • 命名要带时序词,如 BeforePersist()AfterSave(),避免逻辑错位(比如在 AfterSave() 里改数据库字段)
  • 别让钩子互相依赖私有字段传值——它们看到的应是同一份输入对象(如 *Order)和上下文

别用 embed 模拟继承,那是陷阱

有人试图嵌入一个“基类”结构体,再覆盖同名方法,但 Go 不支持方法重写:调用 p.Transform() 仍会走到嵌入字段的方法里,除非你手动重定向。

  • 嵌入只提升字段访问,不改变方法绑定目标
  • 结果是钩子被跳过、日志没打、事务没提交,问题极难定位
  • 正确做法是:所有可变步骤都声明为接口方法,由 concrete struct 显式实现
  • 如果真需要复用逻辑(如通用日志),抽成独立函数,再在具体实现里调用,而不是塞进“父结构体”

最容易被忽略的一点是:模板方法不是为了“看起来像 OOP”,而是为了确保流程顺序不可篡改、每一步意图清晰可验证。一旦开始用反射、类型断言或 init 自动注册,你就已经偏离了 Go 的设计哲学。

text=ZqhQzanResources