你是否曾在不同类中复制粘贴相似逻辑,并想着“这好像不太对”?在 iOS 应用里,当你第三次复制某个页面的数据加载代码时,手就会停下来。
这时,Template Method Pattern 就派上用场了。
简单来说,这种模式把完整流程(骨架)固定在一个地方,只让各个实现填充不同的步骤。在 Swift 中,使用协议扩展比继承更简洁。
本文将依次介绍 Template Method Pattern 是什么、为什么在 Swift 中协议扩展更接近理想方案,以及如何用实际代码搭建骨架。
先来看看核心总结
- Template Method Pattern 将“固定流程 + 可替换步骤”分离开来
- 传统做法是通过父类继承实现,但在 Swift 中协议扩展更合适
- 把骨架放在扩展的默认实现中,把变化部分保留为协议要求
- 摆脱单继承、强耦合等继承限制
什么是 Template Method Pattern?
名字听起来很复杂,但概念很简单。
想象一下菜谱。“准备食材 → 处理 → 烹饪 → 装盘”的顺序是固定的。
但食材和烹饪方式会因菜品而不同。
这里,顺序(骨架)就是 Template Method,而每道菜不同的步骤就是可替换部分。
在一个地方控制流程,只把具体实现委托到外部。
这句话基本可以概括 Template Method Pattern 的全部内容。
从代码来看,情况是这样的。每个页面的数据加载流程都相同:开始加载 → 请求 → 解析 → 更新页面。
不同的只是“请求哪个 URL”和“如何解析收到的数据”。
每次重新编写这套通用流程,都是一种浪费。
Swift 中为什么不用继承,而使用协议扩展?
传统方式是在父类中放置骨架,再让子类重写特定方法。就是 Java 教材里的那种方式。
但在 Swift 中通过类继承实现,会遇到不少限制。
- 类只能单继承。如果已经有一个父类,就无法再继承其他父类
- 完全不能用于 struct 或 enum
- 父子类高度耦合,之后很难替换
协议扩展可以解决其中大部分问题。
把骨架方法作为扩展的默认实现提供,把变化的步骤只声明为协议要求。
这样,无论是 struct 还是 class,只要遵循该协议,就能免费获得通用骨架,不受继承限制。
下面是用协议定义数据加载流程的示例。load()是固定骨架,另外两个是可替换部分。
protocol DataLoadable {
associatedtype Item
var endpoint: URL { get } // 每个页面不同
func parse(_ data: Data) -> [Item] // 每个页面不同
}
extension DataLoadable {
func load() async throws -> [Item] { // 固定骨架
let (data, _) = try await URLSession.shared.data(from: endpoint)
return parse(data)
}
}
load()的顺序由扩展控制。
每个页面只需按自己的方式实现endpoint和parse。
实际搭建一下骨架
现在来看看遵循这个协议的一方。因为是遵循而不是继承,所以也可以使用 struct。
struct ArticleLoader: DataLoadable {
let endpoint = URL(string: "https://api.example.com/articles")!
func parse(_ data: Data) -> [Article] {
(try? JSONDecoder().decode([Article].self, from: data)) ?? []
}
}
// 使用: let articles = try await ArticleLoader().load()
如你所见,ArticleLoader没有直接实现load()。
即便如此,它仍然可以调用load(),因为扩展提供了默认实现。
这就是使用协议扩展构建 Template Method 的力量。新增页面时,只需再创建一个实现endpoint和parse的 struct 即可。
改用这种方式后,我把新增页面的代码减少到了一半以下。加载和错误处理逻辑集中到一处后,修复 bug 也容易多了。
需要注意一点:协议扩展的默认实现与 override 的行为略有不同。即使遵循协议的类型定义了同名方法,如果没有将其声明为协议要求,静态分派也可能调用错误的实现。
因此,最好务必将可替换步骤声明为协议正文中的要求。
用简短的问答做个总结
问:那现在可以不用基于继承的 Template Method 了吗?
在 UIKit 这类已经存在类层次结构的地方,通过继承进行 override 有时仍然很自然。根据具体情况选择即可。
问:associatedtype 让我觉得有些复杂。
如果返回类型固定,可以不使用 associatedtype,直接定义普通协议。只有需要泛型时才使用它。
试着用协议扩展搭建骨架后,代码会整理得如此清晰,以至于之前与继承苦苦周旋的经历都显得有些徒劳。
如果你现在有一段不断复制粘贴的逻辑,今天就挑一段提取到协议中吧。很快就能体会其中的好处。加油!

