你是否曾在不同類別中複製貼上相似邏輯,心想「這好像不太對」?在 iOS App 裡,當你第三次複製各畫面的資料載入程式碼時,手往往就會停下來。
這時可以派上用場的工具,就是 Template Method Pattern。
簡單說,這是一種把整體流程(骨架)固定在同一處,只讓各實作填入不同步驟的模式。在 Swift 中,使用協定擴充比繼承更簡潔。
本文將依序說明 Template Method Pattern 的概念、為什麼在 Swift 中協定擴充更接近理想解法,以及如何用實際程式碼建立骨架。
先看重點摘要
- Template Method Pattern 將「固定流程+可替換步驟」分離
- 傳統上透過父類別繼承實作,但在 Swift 中使用協定擴充更合適
- 把骨架放在 extension 的預設實作中,將差異部分保留為協定需求
- 不再受限於單一繼承與高耦合等繼承問題
什麼是 Template Method Pattern?
名稱聽起來很宏大,但概念其實很簡單。
想像一下料理食譜。「準備食材 → 處理 → 烹調 → 擺盤」的順序是固定的。
但食材與烹調方式會因料理而異。
其中順序(骨架)就是 Template Method,而每道料理不同的步驟就是可替換部分。
在同一處控制流程,只把細節實作委派出去。
這一句話幾乎就概括了 Template Method Pattern 的全部。
用程式碼來看,情況是這樣的。每個畫面的資料載入流程都相同:開始載入 → 請求 → 解析 → 更新畫面。
不同的只有「要請求哪個 URL」以及「如何解析收到的資料」。
每次重新撰寫這段共用流程,就是浪費。
Swift 中為什麼不用繼承,而是使用協定擴充?
原本的做法是在父類別中放入骨架,再由子類別 override 特定方法。就是 Java 教科書中的那種方式。
但在 Swift 中用類別繼承實作,會遇到不少限制。
- 類別只能單一繼承。如果已經有一個父類別,就無法再繼承其他父類別
- 完全不能套用在 struct 或 enum 上
- 父子類別緊密耦合,之後很難替換
協定擴充能解決其中大部分問題。
將骨架方法透過 extension 的預設實作提供,並只把不同步驟宣告為協定需求。
如此一來,不論是 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()的順序由 extension 控制。
各畫面只要依自己的方式填入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(),因為 extension 提供了預設實作。
這就是使用協定擴充建立 Template Method 的力量。新增畫面時,只要再建立一個只填入endpoint和parse的 struct 即可。
改用這種方式後,我把新增畫面的程式碼減少到一半以下。載入與錯誤處理邏輯集中在同一處後,除錯也容易許多。
有一點要注意。協定擴充的預設實作與 override 的行為略有不同。即使採用型別定義了同名方法,只要該方法沒有宣告為協定需求,就可能因靜態分派而呼叫到非預期的實作。
因此,最好務必將可替換的步驟宣告在協定本文中,作為協定需求。
簡短 Q&A 總結
Q. 這樣一來,繼承式的 Template Method 就不用了嗎?
在 UIKit 這種既有類別階層的地方,透過繼承 override 有時仍然很自然。請依情況選擇。
Q. associatedtype 讓人有點負擔。
如果回傳型別固定,可以不用 associatedtype,直接定義成一般協定。只有需要泛型時才使用它。
試著用協定擴充建立骨架後,會整理得如此清楚,甚至讓人覺得以前和繼承奮戰有點徒勞。
如果現在有一段持續複製貼上的邏輯,今天就挑一段抽成協定吧。很快就會抓到感覺。加油!

