意圖(Intent)#
動態地為物件附加額外的職責。Decorator 提供了比子類別化更彈性的功能擴展方式。
別名(Also Known As)#
Wrapper
動機(Motivation)#
有時我們想為個別物件(而非整個類別)增加職責。例如,GUI 工具組應該能讓任何 UI 元件加上邊框或捲動功能。
用繼承來加上邊框,會讓每個子類別實例都帶有邊框——選擇是靜態的,客戶端無法控制何時裝飾。更彈性的做法是用另一個物件來「包裹」原始元件,這個包裹物件就是 decorator。
Decorator 的關鍵特性:
- 符合被裝飾元件的介面,對客戶端透明
- 將請求轉發給內部元件,可在轉發前後執行額外操作
- 可以遞迴巢套,堆疊無限數量的職責
例如:一個 TextView 先被 ScrollDecorator 包裹加上捲動功能,再被 BorderDecorator 包裹加上邊框,就得到一個有邊框的可捲動文字視圖。
Decorator 的核心優勢在於組合優於繼承:透過物件組合而非類別繼承來擴展功能,職責可以在執行期動態添加和移除,且不同裝飾器可以自由混搭組合。
適用場景(Applicability)#
- 需要動態、透明地為個別物件加上職責,而不影響其他物件
- 需要可撤回的職責擴展
- 用子類別化來擴展不切實際——可能的獨立擴展太多,會導致子類別數量爆炸;或者類別定義被隱藏、無法繼承
結構(Structure)#
Decorator 持有一個 Component 的引用,其介面與 Component 一致。ConcreteDecorator 繼承 Decorator 並加上額外的職責。客戶端透過 Component 介面操作,不需知道裝飾器的存在。
classDiagram
class Component {
<<interface>>
+Operation()
}
class ConcreteComponent {
+Operation()
}
class Decorator {
-component
+Operation()
}
class ConcreteDecoratorA {
-addedState
+Operation()
}
class ConcreteDecoratorB {
+Operation()
+AddedBehavior()
}
Component <|.. ConcreteComponent
Component <|.. Decorator
Decorator <|-- ConcreteDecoratorA
Decorator <|-- ConcreteDecoratorB
Decorator o--> Component參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Component | VisualComponent | 定義可被動態加上職責的物件介面 |
| ConcreteComponent | TextView | 被附加額外職責的具體物件 |
| Decorator | - | 持有 Component 引用,定義符合 Component 的介面 |
| ConcreteDecorator | BorderDecorator、ScrollDecorator | 為元件增加具體的職責 |
協作方式(Collaborations)#
- Decorator 將請求轉發給其 Component 物件,可在轉發前後執行額外操作
優缺點(Consequences)#
- 比靜態繼承更彈性:職責可在執行期動態添加和移除,不需為每種組合建立新類別。同一種裝飾可以套用兩次(如雙重邊框)
- 避免功能臃腫的高層類別:採用按需付費(pay-as-you-go)的方式,從簡單類別開始,用 Decorator 逐步增加功能
- 物件身分問題:被裝飾的物件與原始物件不是同一個物件,不應依賴物件身分(object identity)
- 大量小物件:使用 Decorator 的設計會產生許多外觀相似的小物件,差異僅在於連接方式,可能增加學習和除錯難度
實作要點(Implementation)#
- 介面一致性:Decorator 必須符合 Component 的介面,所有 ConcreteDecorator 需繼承共同的類別
- 省略抽象 Decorator 類別:只需要加一種職責時,可以直接將轉發邏輯合併到 ConcreteDecorator 中
- 保持 Component 類別輕量:Component 應專注於定義介面,不應儲存資料。若 Component 過於龐大,Decorator 也會變得笨重
- 改變「外皮」vs. 改變「內臟」:
- Decorator 從外部改變物件行為(改變外皮)
- Strategy 從內部改變物件行為(改變內臟)
- 當 Component 本身就很重量級時,Strategy 可能是更好的選擇
當 Component 類別已經很龐大時,考慮使用 Strategy 取代 Decorator。Strategy 讓元件將行為委派給獨立的策略物件,策略物件可以有自己專用的介面,即使 Component 很重也能保持策略的輕量。MacApp 和 Bedrock 的「adorner」和「behavior」物件就是這種做法。
已知應用(Known Uses)#
- InterViews、ET++、ObjectWorks\Smalltalk:廣泛使用 Decorator 為 widget 加上圖形裝飾
- InterViews DebuggingGlyph:在轉發 layout 請求前後輸出除錯資訊
- ParcPlace Smalltalk PassivityWrapper:啟用或停用使用者互動
- ET++ Stream 類別:Stream 是基礎抽象,StreamDecorator 子類別如 CompressingStream(壓縮)、ASCII7Stream(7-bit ASCII 轉換)可以自由組合。例如:FileStream 先包一層 CompressingStream 再包一層 ASCII7Stream
相關模式(Related Patterns)#
- Adapter:Adapter 改變物件的介面;Decorator 只改變職責,不改變介面
- Composite:Decorator 可視為只有一個元件的退化 Composite,但目的不同——Decorator 是加上職責,不是用來聚合物件
- Strategy:Decorator 改變外皮,Strategy 改變內臟——兩種改變物件的替代方式