第 8 / 11 部 待錄

行為型模式I-策略與範本方法

《Design Patterns 設計模式(GoF)》Podcast 準備稿:行為型模式 I — Strategy 與 Template Method

書名: 設計模式 Design Patterns(Elements of Reusable Object-Oriented Software) 作者: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides(Gang of Four) 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Design Patterns.抽換演算法的兩種姿勢:Strategy vs Template Method (8/11) 涵蓋範圍: 行為型模式總覽 + Strategy、Template Method(深講)

背景速覽

進入 GoF 最大的一類——Behavioral Patterns(行為型模式),整整 11 個。這類模式關注的是「演算法」和「物件之間的職責怎麼分配」,描述的是物件間的通訊模式,把你的注意力從「執行期難追蹤的控制流」轉向「物件怎麼互連」。

這集挑兩個最常用、又剛好構成經典對照的模式:Strategy(策略)Template Method(範本方法)。它們解決同一個問題——「演算法的某部分要能變」——但用兩種相反的姿勢:Strategy 靠組合(換一個策略物件),Template Method 靠繼承(子類別覆寫步驟)。這正是「組合 vs 繼承」這條全書主軸最乾淨的一次對決。

一句話重點

Strategy 把一整族可互換的演算法各自封裝成物件、靠組合抽換,讓演算法獨立於使用它的 client;Template Method 在父類別定演算法骨架、把可變步驟留給子類別繼承覆寫——兩者解同一題,一個用組合、一個用繼承,是 GoF 全書「組合 vs 繼承」的最佳對照。

值得討論的重點

1. 行為型模式的全景地圖

開場先給聽眾一張地圖,11 個模式怎麼分:

  • Class 版(靠繼承):Template Method(最簡單最常用)、Interpreter。
  • Object 版(靠組合) 又分兩支:
    • 協作型(一群對等物件怎麼協作):Mediator、Chain of Responsibility、Observer。
    • 封裝型(把行為封裝進物件再委派):Strategy、Command、State、Visitor、Iterator。

這張地圖整季會反覆用到。這集落在「Template Method(class 版)」和「Strategy(封裝型)」。

2. Strategy:把演算法做成可抽換的物件

Intent:定義一族演算法,各自封裝、可互換,讓演算法的變化獨立於使用它的 client。別名 Policy

動機(呼應 EP2 Lexi 排版):文字斷行有很多演算法。全寫死在一個類別裡會出三個問題——client 變複雜、不同情境要不同演算法、難新增修改。解法是把每種斷行演算法封裝成 strategySimpleCompositorTeXCompositorArrayCompositorComposition 持有一個 Compositor,要重排就委派給它,client 透過安裝不同 Compositor 來選策略。

優點要講:演算法家族可重用用組合取代子類別化(直接繼承 Context 會把行為焊死,難獨立換)、消除大量 if/switch(把分支變成「裝哪個策略」)。

核心句:把演算法從使用它的類別中解耦,用組合而非繼承來換行為——這就是「favor composition」的正字標記。

缺點也誠實講:client 得懂各策略的差異才能選、Strategy 和 Context 之間的通訊可能傳了某些策略不需要的參數、物件數量增加(可用 Flyweight 共享無狀態策略緩解)。

3. Template Method:父類別定骨架,子類別填步驟

Intent:在一個操作裡定義演算法的骨架,把某些步驟延遲到子類別,讓子類別在不改變演算法結構的前提下重定義特定步驟。

動機:框架的 Application.OpenDocument() 定義了開檔的完整流程:檢查能否開 → 建 Document → 加入集合 → 讀檔。這就是 template method——它用抽象操作定骨架,子類別覆寫 DoCreateDocumentDoRead 來填具體行為。步驟順序固定,每步的實作自由。

最重要的觀念(一定要唸):Template Method 產生一種反轉的控制結構,叫 Hollywood 原則——「別打電話給我們,我們會打給你(Don’t call us, we’ll call you)」。父類別呼叫子類別的操作,而不是反過來。 這正是「框架」和「函式庫」的根本差別:函式庫是你呼叫它,框架是它呼叫你。Template Method 就是這個「控制反轉」的最小單位。

實作要點:要分清哪些是 hook(可選覆寫)、哪些是 abstract(必須覆寫),常用命名慣例標示(MacApp 用 Do- 前綴)。還有個好處:用 hook 取代「覆寫 + 手動呼叫 super」——後者一旦忘記呼叫 super 就破壞流程,前者讓父類別掌控流程、子類別只填空,更安全。

4. Strategy vs Template Method:組合 vs 繼承的經典對決(本集高潮)

GoF 自己在 Related Patterns 點明:

  • Template Method 透過繼承改變演算法的部分步驟——是「class 版」、編譯期決定、整批步驟綁在繼承鏈上。
  • Strategy 透過委派改變整個演算法——是「object 版」、run-time 可換、演算法獨立成物件。

選擇直覺:你只想讓「某幾個步驟」可變、而且編譯期就定 → Template Method(簡單,但用了繼承);你想讓「整個演算法」可 run-time 抽換、或想避免繼承 → Strategy(彈性,但物件多一點)。

而現代設計的天平,普遍偏向 Strategy——因為「組合勝過繼承」。Template Method 的繼承耦合,讓它在大型框架裡容易變脆。

注意事項

⚠️ Template Method 用的是繼承,這在『偏好組合』的現代有點逆風。 GoF 把它列為「最常用的行為 class 模式」,但繼承的老問題它都有——父子緊耦合、父類別一改子類別全連坐、難以 run-time 變更。所以雖然它簡單好用,現代很多場合會優先考慮用 Strategy(組合)達成類似效果。講的時候要平衡:Template Method 沒有錯,但它代表的是「用繼承解問題」的那條路,而那條路在今天通常是次選。

⚠️ 別把 Strategy 套在「永遠只有一種演算法」的地方。 Strategy 的價值來自「真的有多種演算法要切換」。如果你的系統實際上永遠只有一個策略,硬上 Strategy 只是徒增一個介面、一個物件、一層間接——典型的 patternitis。先確認「變化真的存在」,再封裝它。

專家補充

💡 在有 first-class function 的語言裡,Strategy 幾乎『消失』成一個函式參數。 GoF 用「策略物件 + 介面 + 子類別」是因為 1994 年的 C++/Smalltalk 沒有方便的函式傳遞。今天在 Kotlin/JavaScript/Python,你想換演算法,直接傳一個 lambda 進去就好——sort(list, comparator) 裡那個 comparator 就是 Strategy,但你根本不會意識到自己在用「模式」。這是 EP1「模式是補語言不足的補丁」最乾淨的例證:語言給了你 first-class function,Strategy 就退化成一個再普通不過的高階函式。

💡 Template Method 是『框架』的靈魂,理解它就理解了控制反轉(IoC)。 你用過的每個框架——Spring 的生命週期回呼、React 的 render/useEffect、JUnit 的 setUp/tearDown、Android 的 onCreate——骨子裡都是 Template Method:框架定好流程骨架,你填空(覆寫 hook)。「Don’t call us, we’ll call you」這句話,是分辨「函式庫」和「框架」的試金石。這集講透它,聽眾對「什麼是框架」會有質的提升。

💡 這對模式是理解整個行為型分類的鑰匙。 行為型 11 個模式,本質都在回答「會變的行為,要放哪、怎麼換」。Strategy 和 Template Method 是這個問題最純粹的兩個答案(組合放法 vs 繼承放法)。後面的 Command、State、Observer……都可以看成「在特定情境下,把行為封裝/委派」的變體。先把這對講透,後面三集會輕鬆很多。

討論問題

🎙️ 開場鉤子:「list.sort(comparator)——你寫過無數次。那個 comparator,就是一個 Strategy。今天我們要講的,是『讓演算法可以抽換』這件事的兩種姿勢:一種傳物件進去(Strategy),一種讓你繼承後填空(Template Method)。它們是 GoF 全書『組合 vs 繼承』的最後對決。」

🎙️ 自問自答:「Strategy 和 Template Method 都讓演算法可變,差在哪?」——一個用組合(換策略物件,run-time 可切,整個演算法都能換),一個用繼承(子類別覆寫步驟,編譯期定,只能換骨架裡的某幾步)。現代天平偏 Strategy,因為組合勝過繼承。

🎙️ 帶走的一題:「想想你用過的框架——它有沒有叫你覆寫某個 onXxxsetUprender?那就是 Template Method 在運作:它定流程、你填空。下次你寫 override,意識一下:是你在呼叫框架,還是框架在呼叫你?」

更大範圍關聯

  • Strategy 與 first-class function:在 FP 與現代語言中 Strategy 退化為高階函式,是「模式被語言吸收」的代表(見《Functional Design》對模式換形的論述)。
  • Template Method 與控制反轉/框架:它是 IoC 的最小單位,與 Spring、React 等框架的回呼機制一脈相承,也與《Clean Architecture》的「框架在外圈、它呼叫你」呼應(見 [軟體工程領域切分藍圖])。
  • 組合 vs 繼承的全書主軸:這對模式是 GoF「favor composition over inheritance」最清楚的對照,與 EP1 的核心原則直接扣合。
  • 與 Refactoring 的接點:「Replace Conditional with Polymorphism」「Replace Inheritance with Delegation」等重構手法,正是走向 Strategy 的路徑(見 [軟體工程領域切分藍圖])。

錄製建議

  • 建議時長:約 23-25 分鐘。配比:行為型全景地圖約 4 分、Strategy 約 8 分、Template Method 約 7 分(含 Hollywood 原則)、兩者對照約 4 分。
  • 討論策略:用 sort(comparator) 這個人人寫過的例子開場,瞬間拉近 Strategy。Template Method 段務必把「Hollywood 原則 / 函式庫 vs 框架」講成記憶點。本集高潮是「組合 vs 繼承」的對決,建議並排比較後明確給出「現代偏 Strategy」的判斷,並用 FP 把 Strategy「退化成 lambda」這個料收尾——呼應全季主題。
  • 先給行為型全景地圖很重要,否則接下來三集 11 個模式會讓聽眾迷路。