GoF 模式的函數式形態

★★★★Studied

FromFunctional Design

📚 From the Books

GoF 模式的函數式形態ConceptParadigms

有個常見迷思:「設計模式只是 OO 語言缺陷的繞路,函數式語言不需要它們」。這部分對——某些模式確實含有「繞開語言限制」的腳手架;但整體不對。把不可變性納入考慮後,多數 GoF 模式在函數式語言裡依然成立,只是換了更輕的形式。只要有像樣的多型機制,函數式語言就跟 OO 一樣 OO。

🧠 When to Use

TIP

訊號:你在 Kotlin/Scala 這類混合範式語言裡,正準備為一個 lambda 能解的事開 interface XxxCommand + 一堆實作類——先問:這個模式補的語言缺口,在我的語言裡還在嗎?

⚖️ 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) }) // 回傳新樹
}
  • Composite 的重點是把「複數」隱藏起來:外部看 composite 與單一元素一樣都只是 Shape。別偷懶在外面 for 迴圈遍歷——那把複數洩漏出去了。
  • Decorator 同理(如「記錄所有操作」的 journaled-shape):實作同介面、持有內部物件、先做增強再委派。不改任何既有元素就加了新功能,這就是 OCP
  • 兩者都能寫成完全純函數版:每次操作回傳新的資料結構。
Visitor —— 雙重分派只是 closed class 的副產品
  • GoF 的 Visitorshape.accept(v) →(多型)Square.acceptv.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 把這相依反轉:具體型別仍是插件,但透過工廠介面建立。
  • 若工廠為「每種產品各開一個方法」(makeCirclemakeSquare),新增產品就得改介面——90 度旋轉又來了。
  • 解法:用不透明 token 當產品種類(:circle"square"、甚至 12 都行)。關鍵是這些 token 在邊界線上方完全不被認識,新增產品時邊界上方一行都不必改。
  • 代價:靜態語言中這犧牲了型別安全(錯誤變成 runtime 例外)——這是「軟體物理定律」:要在架構邊界維持 OCP,就得在邊界處放棄部分型別安全

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading