《Design Patterns 設計模式(GoF)》Podcast 準備稿:行為型模式 IV — Visitor、Interpreter + 全書結語
書名: 設計模式 Design Patterns(Elements of Reusable Object-Oriented Software) 作者: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides(Gang of Four) 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Design Patterns.Visitor/Interpreter,與一個 30 年經典的真正遺產 (11/11) 涵蓋範圍: Visitor、Interpreter(深講)+ 第6章 結語(共同詞彙、Alexander、模式的當代命運)
背景速覽
最後一集,分兩半。前半收掉行為型最後兩個、也是最「硬核」的模式:Visitor(訪問者)——在不改元素類別的前提下加新操作,但有個著名的取捨;Interpreter(直譯器)——把一種小語言的文法表示成類別樹再解釋它。
後半是整季的收束——GoF 的結語。我們要回答整季最大的問題:這本 1994 年的書,30 年後留下的真正遺產是什麼? 答案不是 23 個模式本身,而是「共同設計詞彙」這件事。我們也要誠實算總帳:哪些模式過時了、哪些在函數式語言裡「換了形狀」、什麼是 patternitis、以及這本書的思想源頭——建築師 Christopher Alexander。把整季的伏筆,這集一次收齊。
一句話重點
Visitor 讓你在不動元素類別的前提下加新操作(代價是不能輕易加新元素),Interpreter 把小語言的文法做成類別樹來解釋;而 GoF 留給世界的真正遺產,不是這 23 個模式,而是給設計招式命名、建立一套讓工程師能彼此溝通的共同詞彙——模式會過時、會被語言吸收,但「為設計命名」這件事永遠不會。
值得討論的重點
1. Visitor:加操作很容易,加元素很痛苦
Intent:表示一個作用於物件結構各元素的操作,讓你不修改元素類別就能定義作用於它們的新操作。
動機:編譯器有一棵 AST,要對它做型別檢查、最佳化、程式碼生成、格式化……如果把這些操作全塞進各個節點類別,型別檢查的 code 會和格式化的 code 混在一起,每加一個操作還要重編所有節點類別。
Visitor 的解法:把相關操作打包成一個獨立的 visitor,遍歷時被各元素「接受」。TypeCheckingVisitor、CodeGeneratingVisitor 各自一個類別;元素提供 Accept(Visitor),透過雙重分派(double dispatch) 呼叫 visitor 上對應的 VisitXxx。新增操作 = 新增一個 visitor 子類別,完全不動元素類別。
但這裡有 GoF 全書最著名的取捨,務必講透——這叫 Expression Problem(表達式問題):
- Visitor 讓「加操作」很容易(加一個 visitor),但讓「加元素」很痛苦(每加一個 ConcreteElement,得在 Visitor 介面加一個
VisitXxx,並在所有 ConcreteVisitor 裡實作它)。 - 反過來,「把操作放進元素類別」讓「加元素」容易、「加操作」痛苦。
選 Visitor 的唯一判準:你的元素結構穩定,但操作經常新增。 編譯器 AST 就是典型——節點種類很少變,但要對它做的分析一直在加。若元素常變,Visitor 是負債。
順帶提缺點:Visitor 常需要元素提供足夠的公開介面,可能破壞封裝。
2. Interpreter:把文法變成類別樹(速覽偏深講)
Intent:給定一種語言,定義其文法的表示法,以及一個用該表示法解釋句子的直譯器。
動機:某類問題反覆出現時,可以把問題實例表達成一種簡單語言的句子,再寫個直譯器來解。經典例子:正規表達式——與其為每個 pattern 寫客製搜尋,不如把 pattern 表達成正規表達式,再由直譯器解釋。
做法:每條文法規則對應一個類別。正規表達式拆成 LiteralExpression、AlternationExpression、SequenceExpression、RepetitionExpression,一個句子由這些類別的實例組成一棵 AST,遞迴呼叫 Interpret 解整棵樹。
取捨要誠實:文法簡單時很優雅、容易用繼承擴展;但文法一複雜,類別就爆炸——每條規則至少一個類別。GoF 自己建議:實務上常搭配 parser generator(用工具做 parsing,Interpreter 只負責 AST 的解釋邏輯),別硬要全手寫。而且如果要對 AST 頻繁新增「解釋方式」,那又該換 Visitor 了。
點睛:Interpreter 其實是好幾個模式的綜合——AST 本身是 Composite,終端符號共享可用 Flyweight,走訪用 Iterator,新增操作用 Visitor。它是「模式交織」的活範例,剛好接到結語。
3.(結語上)GoF 的真正遺產:共同設計詞彙
GoF 在結語坦白:這本書沒有發明任何新演算法、新理論,它只是「記錄既有的設計經驗」。但這件事意義深遠——它給了這些經驗標準化的名字與定義。
最大的影響是 common design vocabulary(共同設計詞彙):
- 專家的知識不只是語法,更是「演算法、資料結構、慣用語」這些高層結構。
- 有了模式名字,設計師能在「比程式語言更高的抽象層次」討論系統——直接說「這裡用 Observer」「把這些做成 Strategy」,一個詞取代十分鐘白板。
- 它也是文件與學習的輔助:用模式描述系統,讀者知道模式名就懂設計意圖,不必逆向工程整個架構。GoF 說,初學者覺得大型 OO 系統難讀,很大程度是因為「不認識其中的模式」。
這就是為什麼這本書 30 年不死:模式可能過時,但「為設計命名、建立共同語彙」這個貢獻是永恆的。
4.(結語下)誠實算總帳:過時、換形、與 patternitis
整季的伏筆,這裡收齊。對聽眾要講一個平衡、成熟的結論:
- 有些模式過時/該退休:Singleton 已被廣泛視為反模式(EP4)。
- 有些模式被語言吸收:Iterator 變
for-each、Strategy 變 lambda、Command 的思想滲進 Event Sourcing(EP8/9/10)。 - 有些模式在 FP 裡換了形狀:函數式語言用 first-class function、pattern matching、algebraic data types,讓 Strategy、Command、Visitor、State 等「換一種更輕的長相」存在——Visitor 的 double dispatch 在 FP 裡就是一個 pattern matching;Strategy 就是傳函式。模式沒消失,是換了載體。
- 過度套用是真實的病——patternitis:明明一個 if 就解決,硬上三層抽象。GoF 自己在導論就警告「模式靠增加間接換彈性,會增複雜、犧牲效能,只在需要時用」。整季的核心提醒:模式是工具不是目標,先有問題、再找模式,不是先愛上模式、再去找地方套。
最後扣回思想源頭:GoF 的 pattern 概念整個借自建築師 Christopher Alexander(《A Pattern Language》)。Alexander 談建築與社區的反覆結構,還提出「pattern language」——讓 pattern 彼此交織生成更大整體。GoF 在結語引用 Alexander 的話作結:最好的設計,是讓許多 pattern 在同一空間中重疊、交織(dovetail and intertwine),建築才會密集而深刻——而不是把 pattern 鬆散地組裝起來。這句話,也正是整本書、整季的最高境界。
注意事項
⚠️ Visitor 是 GoF 裡最容易『學了反而害人』的模式,務必把它的代價講到底。 很多人學會 Visitor 後到處套,結果碰到「需求是常加新的元素類別」的場景,每加一個元素就要改一堆 visitor,痛不欲生。Visitor 只在『元素結構穩定、操作常變』時才划算——這個前提沒滿足就別用。這是整季「帶腦讀經典」最重要的收尾示範之一。
⚠️ 結語別把這本書講成『過時了,可以不讀』——那是另一個極端的錯。 平衡點是:具體的 23 個模式有些過時、有些換形,但三條核心原則(封裝變化、組合勝過繼承、針對介面)和「共同詞彙」這個貢獻完全沒過時。 讀 GoF 不是為了背 23 張卡片去套,而是為了內化那幾條原則、學會「為設計命名、用意圖辨識結構」的思維方式。讀經典要帶腦——既不盲信、也不盲棄。
專家補充
💡 「Expression Problem(表達式問題)」是 Visitor 教給你的、比模式本身更深的洞見。 任何資料 + 操作的二維設計,你都得選一個方向容易擴展:物件導向(類別=資料)天生「加資料容易、加操作難」,Visitor 把它翻轉成「加操作容易、加資料難」,但翻轉不能兼得。函數式(用 sum type + pattern matching)天生站另一邊。沒有銀彈——你只能選一個你『更常變』的維度讓它容易。這個取捨思維,遠比「會寫 Visitor」值錢,它讓你看任何架構都多一個透鏡。
💡 GoF 和 Christopher Alexander 的淵源,是這本書最浪漫、也最常被忽略的一面。 Alexander 是建築師,他研究「為什麼有些建築/城市讓人感到活著、有些死氣沉沉」,提出 pattern language。GoF 把這套「為反覆出現的好結構命名」的方法論,整個搬進軟體。所以「design pattern」這個詞,骨子裡帶著一種人文關懷——好的設計像好的建築,是許多小模式交織出的、有生命的整體。這個故事很適合當整季的詩意收尾。
💡 這本書真正改變的,是工程師『討論設計的方式』。 在 GoF 之前,兩個工程師討論設計只能畫圖、比手畫腳;之後,「上個 Observer」「這裡 Strategy 一下」成了行業通用語。即使你覺得某些模式過時,你還是天天在用它建立的詞彙在溝通——這就是它最深的遺產:它沒有發明這些招式,但它給了我們談論這些招式的語言。 一本書能改變一個行業的語言,這就是經典的定義。
討論問題
🎙️ 開場鉤子:「最後一集。我們先解決兩個最硬的模式 Visitor 和 Interpreter,然後問一個大問題:一本 1994 年、用 C++ 寫範例、連作者都說 Singleton 該拿掉的書,憑什麼 30 年後還是必讀?答案可能會顛覆你——它的價值,不在那 23 個模式。」
🎙️ 自問自答:「學完 23 個模式,我到底該帶走什麼?」——三條原則(封裝變化、組合勝過繼承、針對介面)+ 一個能力(用『意圖』辨識設計、用模式名跟人溝通)。23 個模式會過時,這兩樣不會。
🎙️ 帶走的一題:「回顧這一季,挑一個你最有共鳴的模式。但別急著去套它——先在下一次設計討論時,試著用它的名字跟同事溝通:『這裡其實是個 Observer 吧?』看看一個詞能不能取代十分鐘的解釋。那一刻,你就真正繼承了 GoF 的遺產。」
更大範圍關聯
- Refactoring(重構到模式):Fowler《Refactoring》+《Refactoring to Patterns》是 GoF 的動詞版——GoF 給終點(該長成什麼),Fowler 給路徑(怎麼演化過去)。讀完 GoF 接 Refactoring 最自然(見 [軟體工程領域切分藍圖])。
- A Philosophy of Software Design(Ousterhout):對「為套模式而增加層數」提出健康質疑,主張深模組、減少複雜度——是 GoF 最好的對照組與煞車。
- Head First Design Patterns:用大量圖解、Java 重講同一批模式的入門替身,適合「先讀它、再回來啃 GoF」。
- Functional Design(與 FP 視角):闡述 Strategy/Command/Visitor/State 等模式在函數式語言中如何「換形」成 first-class function、pattern matching、ADT——回答整季「模式在 FP 換形/消失」的核心提問(見 [軟體工程領域切分藍圖])。
- 思想源頭 Christopher Alexander:《A Pattern Language》《The Timeless Way of Building》——理解 GoF 的 DNA,也理解「pattern」一詞的人文根源。
錄製建議
- 建議時長:約 26-30 分鐘(收官集,可略長)。配比:Visitor 約 8 分(含 Expression Problem)、Interpreter 約 5 分(速覽 + 模式交織)、結語約 13-15 分(共同詞彙、過時/換形/patternitis 總帳、Alexander 收尾)。
- 討論策略:前半 Visitor 一定要把「Expression Problem」這個比模式更深的洞見講出來,這是讓資深聽眾驚艷的點。Interpreter 速覽即可,重點是用它的「模式交織」性質橋接到結語。後半結語是整季的靈魂——建議結構為:先講「真正遺產=共同詞彙」→ 再誠實算總帳(過時/被語言吸收/FP 換形/patternitis)→ 最後用 Alexander 的「pattern 交織出有生命的整體」詩意收尾。
- 這是 11 集的終點,記得回顧整季走過的三大類、把「帶腦讀經典」這條定調再強調一次,給聽眾一個完整、成熟、不教條的句點。