把物件組成樹狀結構,讓 client 用同一套介面對待「單一葉子」與「物件群組」,遞迴處理整棵樹時不必區分兩者。
🧠 When to Use
TIP
訊號:你的資料是部分-整體的層級結構(檔案/資料夾、UI 元件、選單),而 client 不想為「單個」和「一群」寫兩套邏輯。
- 要表示樹狀的整體與部分階層。
- 希望 client 一致對待葉子節點與組合節點。
- 操作(計算大小、渲染、序列化)要能遞迴套用到整棵樹。
⚖️ Structure & Variants
經典版 — 葉子與組合同介面
interface FileNode { fun size(): Int }
// 葉子
class File(private val bytes: Int) : FileNode {
override fun size() = bytes
}
// 組合節點:持有子節點,遞迴彙總
class Folder(private val children: List<FileNode>) : FileNode {
override fun size() = children.sumOf { it.size() }
}
val tree = Folder(listOf(File(100), Folder(listOf(File(20), File(30)))))
tree.size() // 150,client 不必區分 File / Folder- 關鍵:Folder 也是 FileNode,所以可以無限巢狀、統一呼叫。
安全性 vs 透明性的取捨
- 透明版:把
add/remove放在共同介面——client 全透明,但葉子得提供無意義的 add(或丟例外)。 - 安全版:
add/remove只放在 Composite——型別安全,但 client 得做型別判斷。 - GoF 偏透明(重一致性);實務看是否常需動態增刪子節點。
⚠️ Misuse & Anti-patterns
- 資料根本不是樹:硬套 Composite 只是徒增葉子/組合的型別負擔。
- 葉子被迫實作群組方法:透明版下
File.add()變成空實作或拋例外,是設計味道。 - 遞迴無防護:環狀引用或過深巢狀會 stack overflow,需限制深度或偵測環。
🔑 Takeaways
- 解決的是「部分-整體的一致處理」:葉子與組合節點共用介面,遞迴操作整棵樹。
- 核心前提:組合節點也實作該介面並持有子節點,才能無限巢狀。
- 常與 Iterator(走訪)、Visitor(分型別操作)搭配。
No notes yet — jot your takeaways or Q&A here.