用「包一層」的方式,在不改原物件、也不爆炸式繼承的前提下,動態疊加功能。裝飾者與被裝飾者實作同一介面,所以可以一層層套下去。
🧠 When to Use
TIP
訊號:你想加功能的組合很多(A、A+B、A+C、A+B+C…),如果用繼承會變成笛卡兒積的子類別爆炸。
- 想對個別物件(而非整個類別)動態加 / 拿掉責任。
- 功能可以自由組合、可疊加(如 IO 串流:buffered + gzip + encrypted)。
- 不想(或不能)改原始類別,但要擴充它的行為。
⚖️ Structure & Variants
經典版 — 同介面包裝
interface Coffee { fun cost(): Int; fun desc(): String }
class Espresso : Coffee {
override fun cost() = 50
override fun desc() = "espresso"
}
// 裝飾者持有同型別的 inner,委派 + 加料
abstract class CoffeeDecorator(protected val inner: Coffee) : Coffee
class Milk(inner: Coffee) : CoffeeDecorator(inner) {
override fun cost() = inner.cost() + 10
override fun desc() = inner.desc() + " + milk"
}
val drink = Milk(Milk(Espresso())) // 疊兩層奶 → 70, "espresso + milk + milk"- 關鍵:裝飾者也是 Coffee,所以能無限套疊、也能傳給任何吃
Coffee的地方。
和 Proxy / Adapter 的差別(同樣是包一層)
- Decorator:介面不變,加強行為,可疊加。
- Proxy:介面不變,控制存取(延遲載入、權限、快取),通常不疊加。
- Adapter:改變介面,把 A 介面轉成 B 介面,目的是相容。
函數式對照 — 高階函數即裝飾
- FP 裡「裝飾」常常就是
f -> g(f):middleware、@decorator、function wrapper。 - 物件版用同介面 + 委派;函數版用 closure 包 closure。本質都是組合優於繼承。
⚠️ Misuse & Anti-patterns
- 層數爆炸難除錯:套了七八層後,stack trace 與行為來源變難追;層次要克制、命名要清楚。
- 裝飾者改了介面:一旦你想「順便多開一個方法」,它就不再能透明替換——那是 Adapter 或新介面,不是 Decorator。
- 其實只想要單一固定組合:那直接寫一個類別就好,硬拆成裝飾鏈是過度設計。
🔑 Takeaways
- 解決的是「功能組合爆炸」:用疊包取代繼承的笛卡兒積。
- 成立前提:裝飾者實作同一介面並委派內層,才能透明替換、無限疊加。
- 和 Proxy(控存取)、Adapter(轉介面)都是「包一層」,靠意圖區分。
No notes yet — jot your takeaways or Q&A here.