Go 模板方法模式(Template Method)实战:用匿名组合模拟继承实现算法骨架

📅 发布时间:2026/10/14 3:16:03
Go 模板方法模式(Template Method)实战:用匿名组合模拟继承实现算法骨架
示例工程【免费下载链接】golang-design-pattern设计模式 Golang实现《研磨设计模式》读书笔记项目地址https://gitcode.com/gh_mirrors/go/golang-design-pattern点击查看免费下载本文基于 golang-design-pattern 仓库中 14_template_method 目录的文档与源码深入讲解模板方法模式的核心思想、Go 语言中模拟继承的独特实现方式以及一个「HTTP/FTP 下载器」完整实例的代码级剖析。读完本文你将掌握如何在 Go 中通过「子类匿名组合父类、父类持有子类引用」的双向组合技巧把通用算法步骤固化在父类、把可变实现延迟到子类写出符合开闭原则的可扩展代码。一、模板方法模式是什么模板方法模式Template Method Pattern是一种行为型设计模式其核心思想是把一段算法或业务流程的骨架步骤顺序定义在父类中父类负责固定每一步的执行时机与顺序而其中某些步骤的具体实现则延迟defer到子类中完成。正如 14_template_method/README.md 所述模板方法模式使用继承机制把通用步骤和通用方法放到父类中把具体实现延迟到子类中实现。使得实现符合开闭原则。开闭原则Open/Closed Principle在这里的体现是对扩展开放、对修改关闭——当需要新增一种业务变体时只需新增一个子类覆写相应步骤无需改动父类中已经稳定的算法骨架也无需修改其他已有子类。在 golang-design-pattern 项目中模板方法模式被归类为行为型模式见 README.md 中的行为型模式列表对应实现位于 14_template_method 目录。二、模式角色划分从模式结构上看模板方法模式通常包含两类角色角色职责本仓库对应实现抽象模板Abstract Template定义算法骨架包含固定步骤的调用顺序可包含部分默认实现template结构体及其Download方法具体模板Concrete Template实现父类中未确定的抽象步骤也可覆写override父类默认实现HTTPDownloader、FTPDownloader在本仓库实例中template作为父类把整个下载流程的通用步骤固定下来即准备prepare→下载download→保存save→收尾finish其中download和save的具体实现留给子类并且父类为save提供了一个默认实现。三、Go 模拟继承的两大前提这是本文最核心、也最容易被忽略的知识点。Golang不提供传统面向对象语言如 Java、C的继承机制因此需要用匿名组合anonymous embedding来模拟继承。而由于本模式的特殊性——父类需要反向调用子类的方法——仅仅子类组合父类是不够的还要求父类同时持有子类的引用。README 中专门强调了这一点因为 Golang 不提供继承机制需要使用匿名组合模拟实现继承。此处需要注意因为父类需要调用子类方法所以子类需要匿名组合父类的同时父类需要持有子类的引用。也就是说本仓库的实现形成了双向组合子类 → 父类HTTPDownloader/FTPDownloader匿名组合*template从而继承父类的Download算法骨架和save默认实现父类 → 子类template内部持有implement接口接口的底层指向子类实例从而在骨架中调用子类覆写后的download()/save()。两条链路缺一不可这正是 Go 中实现父类调用子类方法这一模板方法模式核心机制的关键。四、源码逐层解析下载器实例本实例要解决的问题是构建一个统一的下载器无论是通过 HTTP 还是 FTP 协议下载资源其流程骨架一致准备 → 下载 → 保存 → 收尾但下载和保存的具体动作随协议不同而不同。完整源码见 templatemethod.go。4.1 对外接口Downloadertype Downloader interface { Download(uri string) }Downloader是对外暴露的抽象接口统一了所有下载器的入口传入资源地址uri即可开始下载。客户端只需面向该接口编程无需关心底层是 HTTP 还是 FTP。4.2 算法骨架template 与 implementtype template struct { implement uri string } type implement interface { download() save() }template是父类它匿名组合了implement接口——这个组合就是父类持有子类引用的载体。implement接口定义了子类必须提供的两个可变步骤download()与save()。注意implement及其方法均为小写包内私有这保证了外部无法直接干预父类的算法骨架只能在包内通过子类完成扩展。父类通过构造函数注入子类实例func newTemplate(impl implement) *template { return template{ implement: impl, } }newTemplate接收一个满足implement接口的实现即子类实例把它存入template.implement从而建立父类 → 子类的引用关系。4.3 骨架方法 Download固定步骤顺序func (t *template) Download(uri string) { t.uri uri fmt.Print(prepare downloading\n) t.implement.download() t.implement.save() fmt.Print(finish downloading\n) }Download是模板方法它固定了算法的执行顺序记录资源地址t.uri uri打印prepare downloading通用准备步骤父类直接实现调用t.implement.download()——动态分派到子类的具体下载实现调用t.implement.save()——动态分派到子类或父类默认的保存实现打印finish downloading通用收尾步骤。由于t.implement实际指向子类实例download()和save()的调用会在运行时被分派到子类的覆写版本这正是把具体实现延迟到子类的底层原理。4.4 默认实现savefunc (t *template) save() { fmt.Print(default save\n) }父类为save提供了默认实现。任何没有覆写save的子类都可以通过匿名组合继承到这个默认实现从而满足implement接口的约束。这体现了模板方法模式父类提供默认行为、子类按需覆写的设计弹性。4.5 子类一HTTPDownloadertype HTTPDownloader struct { *template } func NewHTTPDownloader() Downloader { downloader : HTTPDownloader{} template : newTemplate(downloader) downloader.template template return downloader } func (d *HTTPDownloader) download() { fmt.Printf(download %s via http\n, d.uri) } func (*HTTPDownloader) save() { fmt.Printf(http save\n) }HTTPDownloader的构建过程是理解 Go 模拟继承的经典范例先创建空壳子类HTTPDownloader{}把子类自身传给newTemplate让父类template持有子类引用再把生成的template赋回downloader.template让子类持有父类引用最终通过匿名组合提升promotionHTTPDownloader自动获得Download方法从而满足Downloader接口。在方法实现上HTTPDownloader同时覆写了download()和save()下载输出download ... via http保存输出http save。注意d.uri是通过嵌入的*template提升访问到父类字段的。4.6 子类二FTPDownloadertype FTPDownloader struct { *template } func NewFTPDownloader() Downloader { downloader : FTPDownloader{} template : newTemplate(downloader) downloader.template template return downloader } func (d *FTPDownloader) download() { fmt.Printf(download %s via ftp\n, d.uri) }FTPDownloader与HTTPDownloader结构完全一致但只覆写了download()没有覆写save()。此时它依赖匿名组合从*template提升得到save()方法因此保存步骤将走父类的默认实现输出default save。这组对比恰好展示了覆写 vs 默认实现两种路径在同一套骨架下的不同表现。五、调用链与双向引用图解以 HTTP 下载为例downloader.Download(http://example.com/abc.zip)的完整调用链如下Downloader.Download(uri) // 接口入口 └─ template.Download(uri) // 经由 HTTPDownloader 内嵌的 *template 提升调用 ├─ fmt.Print(prepare downloading) ├─ t.implement.download() // 接口动态分派 → HTTPDownloader.download() │ └─ fmt.Printf(download %s via http, d.uri) // d.uri 经内嵌 template 提升访问 ├─ t.implement.save() // 接口动态分派 → HTTPDownloader.save() │ └─ fmt.Printf(http save) └─ fmt.Print(finish downloading)从内存结构看运行时存在一个循环引用闭环HTTPDownloader.template → *template同时template.implement → *HTTPDownloader。正是这个闭环让父类调子类、子类用父类成为可能。若把HTTPDownloader替换为FTPDownloader同样的骨架中t.implement.save()会分派到从*template提升来的save()默认实现输出default save——算法骨架零改动行为随子类变化这就是模板方法模式的精髓。六、测试验证Example 测试固化行为仓库通过 templatemethod_test.go 中的 Go Example 测试对两个子类的行为做了固化验证。Go 的 Example 测试会比对程序实际输出与注释中// Output:块不一致即测试失败。func ExampleHTTPDownloader() { var downloader Downloader NewHTTPDownloader() downloader.Download(http://example.com/abc.zip) // Output: // prepare downloading // download http://example.com/abc.zip via http // http save // finish downloading } func ExampleFTPDownloader() { var downloader Downloader NewFTPDownloader() downloader.Download(ftp://example.com/abc.zip) // Output: // prepare downloading // download ftp://example.com/abc.zip via ftp // default save // finish downloading }在仓库根目录执行以下命令即可运行验证需先安装 Go 环境go test ./14_template_method/ -v预期输出中两个测试的执行顺序均为prepare downloading → download → save → finish downloading骨架完全一致差异仅在 download 的具体输出via http/via ftp以及 save 输出http save/default save。其中default save正是 FTPDownloader 未覆写save时继承父类默认实现的直接证据。七、模板方法模式与开闭原则回顾 README 中使得实现符合开闭原则的论断在本实例中可以得到具体印证对扩展开放要新增一种协议如 SFTPDownloader只需新增一个组合*template的结构体实现download()及可选的save()再写一个NewSFTPDownloader()构造函数即可父类template.Download骨架一行不用改对修改关闭下载流程的步骤顺序准备、下载、保存、收尾被固化在父类中任何子类都无法打乱该顺序保证了所有下载器行为的一致性与可维护性。八、Go 实现模板方法模式的注意事项双向组合缺一不可只让子类嵌入父类、不把子类引用注入父类父类便无法调用到子类方法只注入引用、不让子类嵌入父类子类又无法继承骨架与默认实现。本仓库的NewHTTPDownloader/NewFTPDownloader构造函数将两步串行完成是标准写法。方法提升的优先级Go 中结构体自身定义的方法优先于内嵌字段提升的方法。HTTPDownloader显式定义了save()因此分派到子类覆写版本FTPDownloader未定义save()因此提升到父类默认版本。理解这一优先级是预判行为的关键。接口的私有方法约束implement接口的方法均为小写只能在包内实现避免外部随意构造不合法的子类起到一定的封装保护作用。适用场景判断当多个流程共享相同的步骤骨架、仅个别步骤存在差异且差异部分可以稳定抽象为接口方法时适合使用模板方法模式若步骤组合本身频繁变化则应考虑策略模式等其他方案。九、小结模板方法模式通过父类定骨架、子类定细节的职责划分为复用算法结构提供了优雅的解决方案。本仓库 14_template_method 的实例则展示了 Go 语言下的特有实现路径利用匿名组合模拟继承利用接口字段反向持有子类引用实现父类对子类方法的动态调用配合默认实现 按需覆写的方法提升机制最终在缺少继承语法的 Go 中完整还原了模板方法模式的能力并天然满足开闭原则。赞分享示例工程【免费下载链接】golang-design-pattern设计模式 Golang实现《研磨设计模式》读书笔记项目地址https://gitcode.com/gh_mirrors/go/golang-design-pattern点击查看免费下载相关推荐CS-Notes 设计模式精讲模板方法模式Template Method——算法骨架复用与 Java 实战CS Notes 设计模式精讲模板方法模式Template Method——算法骨架复用与 Java 实战 模板方法模式Template Method知识库文档教程DesignPatternsPHP 模板方法模式Template Method实战解析用 PHP 8 抽象骨架定制算法流程DesignPatternsPHP 模板方法模式Template Method实战解析用 PHP 8 抽象骨架定制算法流程 模板方法Template M示例工程教程Java 模板方法模式Template Method Pattern实战用预定义骨架精简复杂算法流程Java 模板方法模式Template Method Pattern实战用预定义骨架精简复杂算法流程 模板方法模式Template Method是 G示例工程教程上一篇Formily Vue 的 ArrayField 组件完全指南数组字段桥接、增删排序与状态恢复原理下一篇OOMWOO RFC Backlog 路线图解析从待规划硬件到后续软件模块的升级机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考