有個常見迷思:「設計模式只是 OO 語言缺陷的繞路,函數式語言不需要它們」。這部分對——某些模式確實含有「繞開語言限制」的腳手架;但整體不對。把不可變性納入考慮後,多數 GoF 模式在函數式語言裡依然成立,只是換了更輕的形式。只要有像樣的多型機制,函數式語言就跟 OO 一樣 OO。
🧠 When to Use
TIP
訊號:你在 Kotlin/Scala 這類混合範式語言裡,正準備為一個 lambda 能解的事開 interface XxxCommand + 一堆實作類——先問:這個模式補的語言缺口,在我的語言裡還在嗎?
- 拿到一個 GoF 模式時,判斷它有多少是「意圖」、多少只是「繞開 closed class 的腳手架」。
- 在有一級函數與不可變資料的語言裡,挑該模式最輕的表達。
- 理解「純函數版模式」怎麼做:回傳新版本,而不是修改自己。
⚖️ Structure & Variants
Command —— 函數本身就是物件
// OO:一個方法的介面
fun interface Command { fun execute() }
// FP:直接傳函數;要帶參數就用 partial application(閉包綁參)
fun someApp(command: () -> Unit) = command()
someApp { doTheThing(theArgument) }- 最單純的 Command 就是一個函數。
- 需要 undo 時:
execute不修改自己,而是回傳一個記錄了執行結果的全新 command,推上 undo stack;undo 時取出呼叫。需求一變複雜(多型、undo),就自然回到 GoF 的標準形式。
Composite / Decorator —— 回傳新版本的純函數
// Composite:對成員廣播;add 回傳新的 composite,不改原物件
data class CompositeShape(val shapes: List<Shape> = emptyList()) : Shape {
fun add(s: Shape) = copy(shapes = shapes + s) // 純函數
override fun translate(dx: Int, dy: Int) =
copy(shapes = shapes.map { it.translate(dx, dy) }) // 回傳新樹
}Visitor —— 雙重分派只是 closed class 的副產品
- GoF 的 Visitor:
shape.accept(v)→(多型)Square.accept→v.visit(this)→(多型)JsonVisitor.visit(Square)。兩次分派=double dispatch:第一次定 shape 子型別、第二次定 visitor 子型別。 - 為什麼這麼複雜?答案是 closed classes:Java/C++ 要為型別加方法就得打開原始檔改它,無法從外部新檔加新方法。
accept+ 雙重分派全是為此搭的腳手架。 - 在支援 open class / multimethod 的語言(Clojure、部分 C#)裡,直接對型別新增一個多方法函數即可,
accept全部消失——GoF 把這個模式描述得有點過度。 - 但「90 度旋轉」的代價還在:新增一種元素,該 visitor 就得改(OCP 違反);解法是把 visitor 的介面與實作拆到不同模組,讓相依都指向高階抽象。
Abstract Factory —— 用不透明 token 消除 90 度旋轉
- 建立物件時往往被迫相依具體類別,跨越架構邊界、違反依賴規則。Abstract Factory 把這相依反轉:具體型別仍是插件,但透過工廠介面建立。
- 若工廠為「每種產品各開一個方法」(
makeCircle、makeSquare),新增產品就得改介面——90 度旋轉又來了。 - 解法:用不透明 token 當產品種類(
:circle、"square"、甚至1/2都行)。關鍵是這些 token 在邊界線上方完全不被認識,新增產品時邊界上方一行都不必改。 - 代價:靜態語言中這犧牲了型別安全(錯誤變成 runtime 例外)——這是「軟體物理定律」:要在架構邊界維持 OCP,就得在邊界處放棄部分型別安全。
⚠️ Misuse & Anti-patterns
- 「FP 不需要模式」教條:只對「純為繞開語言限制」的那部分成立;意圖層面的模式在 FP 依然有用。
- 機械照搬 GoF 腳手架:在有一級函數的語言裡仍寫
accept/visit雙重分派、或為一個 lambda 開一整套 Command 類階層。 - 把 Composite 的複數洩漏到外部:在外面
for遍歷成員,等於沒用 Composite。 - Singleton 想太多:在函數式(或任何)語言裡,需要單例?直接建一個就好,別套 GoF 的雙重檢查骨架。
🔑 Takeaways
- 多數 GoF 模式在 FP 依然成立,且常更輕:Command=函數、Composite/Decorator=回傳新版本的純函數。
- Visitor 的雙重分派、Abstract Factory 的 90 度旋轉,很大程度只是 closed class 的腳手架;open class / multimethod 能讓它們消失。
- 要在架構邊界維持 OCP,常得用不透明 token、並付出型別安全的代價——這是模式背後不變的取捨。
- 只要語言有像樣多型,FP 與 OO 完全相容且互利;FP 的額外限制只是「消除或隔離副作用、偏好不可變」。
No notes yet — jot your takeaways or Q&A here.