軟體設計

Swift 工廠方法模式(Factory Method Pattern):為什麼不用 init

Swift 工廠函式會為建立邏輯加上表達意圖的名稱,並讓回傳型別與重用策略更具彈性。本文整理選擇 static func 而非 init 的判斷標準,以及它與 GoF 工廠方法的差異。

閱讀 4 分鐘
Swift 工廠方法模式(Factory Method Pattern):為什麼不用 init 封面圖

使用 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 一定會回傳新物件,但工廠可以回傳快取值或單例。

第三,可以彈性選擇回傳型別。

依照情況,也能回傳子型別或其他實作。

第四,能更清楚地表達失敗處理。

只加上一行 static func,程式碼就會好讀許多
只加上一行 static func,程式碼就會好讀許多

以下是使用快取的工廠範例。

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 開發愉快!

延伸閱讀