使用 Swift 開發 App 時,一天之內往往會寫上數十次建立物件的程式碼。
但到了某個時候,只靠 init 會不斷遇到不順手的情境。
一開始什麼都用 init,隨著專案變大,很多人便會開始尋找工廠方法模式。
本文會搭配實際程式碼,說明 Swift 工廠方法模式是什麼,以及為什麼要用工廠取代 init。
用一句話說明工廠方法模式?
先直接說重點。
工廠方法會把物件建立邏輯抽離到 init 之外的獨立方法,為它命名,並自由選擇回傳型別。
在 Swift 中,通常是指透過 static func 建立的靜態工廠方法。
本文討論的是以型別方法包裝建立流程的靜態工廠。這與由子類別決定建立型別的 GoF 模式不同;兩者的界線已在工廠方法與抽象工廠的差異中比較。
init 只能在語言規定的規則內運作。
它不能自訂名稱,而且必須建立自己型別的新執行個體。
工廠方法可以解除這些限制。
來看一個簡單的例子。
struct Color {
let r, g, b: Double
// 用名稱表達建立意圖
static func rgb(_ r: Double, _ g: Double, _ b: Double) -> Color {
Color(r: r, g: g, b: b)
}
static func gray(_ v: Double) -> Color {
Color(r: v, g: v, b: v)
}
}
像Color.gray(0.5)這樣呼叫時,只看程式碼也知道它會建立灰色。
為什麼使用工廠而不是 init?
根據我的實際經驗,主要有四個理由。
第一,可以命名。
所有 init 的名稱都一樣,只能靠參數來區分。
如果有多個建構子都接收兩個Double,很容易讓人混淆。
工廠方法則能透過from(hex:)、rgb(_:_:_:)等名稱表達意圖。
第二,不必每次都建立新的執行個體。
init 一定會回傳新物件,但工廠可以回傳快取值或單例。
第三,可以彈性選擇回傳型別。
依照情況,也能回傳子型別或其他實作。
第四,能更清楚地表達失敗處理。
以下是使用快取的工廠範例。
final class IconCache {
private static var store: [String: Icon] = [:]
// 名稱相同時重用已建立的圖示
static func icon(named name: String) -> Icon {
if let cached = store[name] { return cached }
let icon = Icon(name: name)
store[name] = icon
return icon
}
}
多次呼叫相同圖示時,可以節省記憶體。
那麼,現在可以不用 init 了嗎?
不,絕對不是。
工廠不是取代 init,而是包裝 init 的工具。
即使在工廠方法中,最後仍然是呼叫 init 來建立物件。
如果只是簡單的值型別,或建立邏輯很明顯,直接使用 init 會更好。
硬要用工廠包裝,只會讓程式碼變長、變難讀。
工廠真正發揮作用的時機另有其處。
- 建立方式有多種,想用名稱區分時
- 需要快取、物件池等重用機制時
- 想把複雜的初始化流程集中隱藏時
- 想回傳協定型別以隱藏實作時
不符合這些條件時,使用 init 才是正確選擇。
Swift 標準函式庫也會這樣區分使用。
Array(repeating:count:)是 init,但像UIColor.systemBlue這類靜態屬性,其實也算是工廠的近親。
實務上的判斷標準總結
我把自己在感到混淆時使用的標準整理成表格。
| 情境 | 建議 |
|---|---|
| 儲存簡單值 | init |
| 建立方式有多種 | 工廠方法 |
| 重用執行個體・快取 | 工廠方法 |
| 隱藏實作並回傳協定 | 工廠方法 |
| 初始化只需一行 | init |
總結來說,init 處理「如何建立」,工廠則處理「建立什麼,以及為什麼建立」。
兩者不是競爭關係,而是分工不同的好夥伴。
建議先從 init 開始,等建立邏輯變複雜時再移到工廠。
只要記住今天介紹的標準,程式碼就會整潔許多。祝你 Swift 開發愉快!

