《Design Patterns 設計模式(GoF)》Podcast 準備稿:創建型模式 I — 工廠雙雄
書名: 設計模式 Design Patterns(Elements of Reusable Object-Oriented Software) 作者: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides(Gang of Four) 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Design Patterns.不要直接 new:工廠雙雄 (3/11) 涵蓋範圍: 創建型模式總覽(Maze 範例)+ Factory Method、Abstract Factory(深講)
背景速覽
從這集起,我們正式進入 23 個模式的目錄,第一站是 Creational Patterns(創建型模式)——專門處理「物件怎麼被建立」這件事。這類模式有五個,這集先深講最常用、也最容易搞混的「工廠雙雄」:Factory Method 和 Abstract Factory。
創建型模式的存在理由很單純:直接寫 new ConcreteClass() 會讓你的程式碼和「具體是哪個類別」綁死。隨著系統越來越依賴物件組合而非繼承,「建立一個有特定行為的物件」不再只是「new 一下」那麼簡單,於是把「建立過程」本身抽象化、封裝起來,就變得重要。這集會用 GoF 全書共用的 Maze(迷宮)範例串起來,看同一個建迷宮的需求,五種模式各自怎麼解。
一句話重點
創建型模式都在做兩件事——封裝「具體要建哪個 class」的知識、隱藏「實例怎麼被建出來與組裝」;而 Factory Method 用「子類別覆寫一個建立方法」解決,Abstract Factory 則更進一步,解決「一次要建出一整個相容家族的物件」。
值得討論的重點
1. 創建型模式的兩個共通主題,與 Maze 範例
開場先給聽眾一張地圖。GoF 說所有創建型模式都圍繞兩個主題:
- 封裝具體 class 的知識——系統只透過抽象介面認識物件。
- 隱藏實例的建立與組裝——你能彈性決定 what(建什麼)、who(誰來建)、how(怎麼建)、when(何時建)。
GoF 用一個「電腦遊戲迷宮」當共同範例:CreateMaze 函式要建出 Room、Wall、Door。問題是,如果把這些 new 全寫死在 CreateMaze 裡,想換成「魔法房間 EnchantedRoom、要咒語的門 DoorNeedingSpell」,就得改整個函式。五個創建型模式,全都是在回答同一題:怎麼把「建哪些具體零件」這個決定,從 CreateMaze 裡抽出去。
2. Factory Method:把「new 哪個」交給子類別
Intent:定義一個建立物件的介面,但讓子類別決定要實例化哪個 class。別名叫 Virtual Constructor(虛擬建構子),很傳神。
GoF 的動機例子:一個框架有抽象的 Application 和 Document。Application 知道何時要建 Document(使用者按「開啟」時),但不知道要建哪一種 Document——因為具體 Document 是應用程式特有的。解法:讓 Application 宣告一個抽象的 CreateDocument(),由子類別 MyApplication 覆寫,回傳 MyDocument。這個 CreateDocument 就是 factory method。
核心句子(值得唸出來):「在獨立的操作中建立物件,讓子類別能覆寫建立方式。」這比直接 new 永遠更有彈性。在 Maze 範例裡,就是把 CreateMaze 裡的 new Room() 換成 MakeRoom(),子類別化 MazeGame 就能改建出哪種房間。
權衡要講白:Factory Method 的代價是——你可能只為了換一個產品,就得繼承一個 Creator 子類別,類別數量會膨脹。GoF 也提了「參數化 factory method」(傳參數決定建哪種產品)來緩解。
3. Abstract Factory:一次建出一整個相容家族
Intent:提供一個介面,建立一系列相關或相互依賴的物件,而不指定具體類別。別名 Kit。
關鍵詞是「產品家族」。回想 EP2 的 Lexi 跨平台外觀:GUIFactory 有 CreateScrollBar、CreateButton、CreateWindow,MotifFactory 回傳整套 Motif widget,MacFactory 回傳整套 Mac widget。Client 只認 GUIFactory 介面,程式碼裡完全不出現 MotifScrollBar 這種具體名字。
三大優點:隔離具體類別、換家族超容易(具體工廠只在「實例化的那一處」出現,換掉它就換了整套)、保證家族一致(同一工廠出來的產品天生相容,不會 Motif 按鈕配 Mac 捲軸)。
但最大的缺點要明講:很難新增「產品種類」。一旦 AbstractFactory 介面定好了能建哪幾種產品,想多加一種(比如 CreateMenu),就得改抽象工廠和所有具體工廠。這就是它的擴展性天花板。
4. 雙雄怎麼選?以及它們其實是搭檔
這是這集的高潮,務必講清楚兩者關係:
- Factory Method 是「一個方法 → 一個產品」,靠繼承覆寫。
- Abstract Factory 是「一個工廠物件 → 一整個產品家族」,靠組合傳入。
- 而 Abstract Factory 最常見的實作方式,就是內部為每種產品放一個 Factory Method。 所以它們不是二選一,常常是「Abstract Factory 由一堆 Factory Method 組成」。
選擇直覺:你要的是「子類別決定建哪個單一物件」→ Factory Method;你要的是「一次切換一整組相容物件」→ Abstract Factory。
注意事項
⚠️ 「工廠」這個詞在業界被嚴重濫用,GoF 的定義其實很精確。 現實中你會看到一堆叫 XxxFactory 的東西,很多根本不是 GoF 的這兩個模式,只是「有個方法幫我 new 東西」的 Simple Factory(GoF 沒有這個正式模式)。錄的時候要幫聽眾把「GoF 的 Factory Method / Abstract Factory」和「日常隨口說的工廠」分開——不然會越聽越亂。
⚠️ 別為了「以後可能換實作」就預先套 Abstract Factory。 它一旦上了,新增產品種類的成本很高(要改所有工廠),而且增加了一整層間接。只有當你真的有多個產品家族要切換(跨平台、跨主題、測試替身 vs 正式實作)時才划算。否則一個簡單的建構函式就夠了——又是 patternitis 的高發區。
專家補充
💡 「依賴反轉」和「工廠」是同一枚硬幣的兩面。 你之所以要工廠,是因為你想讓高層程式碼依賴抽象、而不是依賴具體類別。這正是 SOLID 裡的 DIP(依賴反轉原則)。現代的 DI 容器(Spring、Guice、.NET 的 DI)某種程度上就是「把 Abstract Factory 這件事自動化、外部化」——你不再手寫工廠,而是讓容器幫你組裝。聽眾如果用過 DI 框架,這集能讓他恍然大悟:原來我天天在用工廠的進階版。
💡 在現代語言裡,Factory Method 常常『退化』。 在有 first-class function 的語言(JavaScript、Kotlin、Python),你常常不需要繼承一個 Creator 子類別來覆寫 CreateXxx——直接傳一個建構函式或 lambda 進去就好了。GoF 用「子類別覆寫」是因為當年 C++/Smalltalk 的慣用法。這是 EP1 預告的「模式是補語言不足的補丁」的第一個實例——語言進步了,模式就變輕甚至消失。
💡 Maze 範例其實是 GoF 的高明教學設計。 同一個建迷宮需求,被五個模式輪流解一遍,等於把「同一問題的不同切法」攤在你面前對照。建議錄的時候始終回扣迷宮,聽眾才不會在五個抽象之間迷路。
討論問題
🎙️ 開場鉤子:「new 是程式設計師學會的第一個關鍵字之一。但 GoF 花了整整一類模式告訴你:很多時候,直接 new 是個壞主意。為什麼?因為 new 一個具體類別,等於把你的程式碼焊死在它身上。」
🎙️ 自問自答:「Factory Method 和 Abstract Factory,到底差在哪?」——一句話:前者是『子類別決定建哪一個』,後者是『一個工廠物件交付一整個相容家族』。而且後者通常由一堆前者組成——它們是搭檔,不是對手。
🎙️ 帶走的一題:「翻翻你的程式碼,找一個直接 new 具體類別的地方。問自己:如果哪天要換成另一個實作(比如測試時想換成假的),我得改幾個地方?如果答案是『很多地方』,那裡就是工廠想幫你的。」
更大範圍關聯
- 與 SOLID 的 DIP:工廠模式是依賴反轉原則的具體手法,與《Clean Architecture》(Uncle Bob) 的「依賴指向抽象」一脈相承(見 [軟體工程領域切分藍圖])。
- 與依賴注入框架:Spring/Guice/Dagger 等 DI 容器是 Abstract Factory 思想的工業化版本——把「組裝物件」這件事抽出來集中管理。
- 在 FP 中的換形:函數式語言用「傳建構函式」「smart constructor」「partial application」取代繼承式工廠(見《Functional Design》對模式換形的討論)。
- 下一集預告:剩下三個創建型模式 Builder、Prototype、Singleton,其中 Singleton 是 GoF 自己都後悔收錄的「反模式候選」——EP4 會好好算這筆帳。
錄製建議
- 建議時長:約 22-25 分鐘。配比:創建型總覽+Maze 約 5 分、Factory Method 約 7 分、Abstract Factory 約 8 分、雙雄對照+DI/FP 補充約 5 分。
- 討論策略:始終用 Maze 範例當錨點,避免抽象漂浮。雙雄的「差異」與「其實是搭檔」這兩點是本集核心,務必講到聽眾能自己複述。結尾把「工廠 = DIP = DI 容器的祖宗」這條線拉出來,給聽眾一個「原來如此」的高潮。
- 提醒:別讓聽眾以為「以後都不該 new」——絕大多數 new 都很好,只在「具體類別會變」時才考慮工廠。