本章主軸#
裝飾者模式(Decorator)讓我們動態地為一個物件附加新責任,而不必走子類化(subclassing)那條路。本章用 e-commerce 案例的「為銷售單加表頭、表尾」需求帶出 Decorator,並說明它在 Java I/O 中的核心地位,以及一個重要訊息:結構不是模式本身,思維才是。
出發點:為銷售單加表頭#

Figure 17-1: SalesOrder 使用 SalesTicket 印單——基本結構
需求:除了原本印單外,要加上表頭。最直覺:在 SalesTicket 裡加旗標。

Figure 17-2: SalesOrder 加入 Header / Footer——但用旗標控制不夠靈活
- 用旗標控制是否印頭、是否印尾
- 一兩種選項可運作,但變化一多就崩潰
- 多種表頭 + 多種表尾可考慮兩個 Strategy
- 但若要同時印多個表頭、表尾,且順序可變,組合會爆炸
此時 Decorator 出場:用一條物件鏈把選用的功能依序串起來,串鏈的構建與使用方完全分離。
GoF 對 Decorator 的意圖描述#
動態地為物件附加額外責任。Decorator 提供子類化以外、更具彈性的擴充方式。
結構:物件鏈#
Component為共同抽象介面ConcreteComponent(如SalesTicket)總是位於鏈的尾端TicketDecorator(抽象)持有下一個Component的參考- 每個具體 Decorator 實作自己的工作,並在前或後呼叫
callTrailer()
abstract class TicketDecorator extends Component {
private Component myTrailer;
public TicketDecorator(Component c) { myTrailer = c; }
public void callTrailer() {
if (myTrailer != null) myTrailer.prtTicket();
}
}
class Header1 extends TicketDecorator {
public Header1(Component c) { super(c); }
public void prtTicket() {
// 印 Header1
super.callTrailer();
}
}要建立 Header1 → Footer1 → SalesTicket:
return new Header1(new Footer1(new SalesTicket()));Decorator 帶來的拆解#
- 如何實作每個附加功能——交給 Decorator
- 如何組合這些功能——交給工廠
- 結果:每個 Decorator 只關心自己的職責,可重用、可任意排序
經典應用:Java I/O#
Java 的 InputStream 家族就是 Decorator:
| 角色 | 範例 |
|---|---|
| 來源(被裝飾者) | FileInputStream、StringBufferInputStream、ByteArrayInputStream |
| 裝飾者 | 衍生自 FilterInputStream 的所有類別 |
「從檔案讀取 → 解壓 → 解密」=
new DecryptStream(new DecompressStream(new FileInputStream(path)));把 Decorator 模式記在腦中,Java I/O 那一票看似讓人困惑的類別,瞬間變得有秩序。
實務筆記#
鏈的建立必須與 Client 解耦#
通常用工廠物件依配置資訊建鏈,否則 Decorator 的彈性會被打折。
應用於測試#
把前置條件、後置條件包在 Decorator 中,可在不改受測物件的前提下,靈活組合各種測試情境。
結構不是模式本身#
「為了傳輸前可能要加密、壓縮、檢查等等」,標準解法是建一條鏈。
但若這些 Decorator 由不同團隊維護,異常處理參差不齊時,整條鏈可能整個炸掉。
真正穩健的做法是引入一個 collection 物件,由它呼叫各個 decorator 並負責例外捕獲。
這時「鏈式結構」已不存在——但 Decorator 模式仍然在發揮作用,因為它的精神(多個可選功能、可組合、不影響 Client)仍被保留。
模式的核心力量(再強調)#
| 力量 | 內涵 |
|---|---|
| 多個可選功能 | 不見得會用、不見得每個都用 |
| 可任意排序 | 不同情境下可能需要不同順序 |
| 與 Client 解耦 | Client 不應知道有這些裝飾物 |
| 容忍實作不一致 | 並不是每個 Decorator 都同樣可靠 |
本章要記住的事#
- Decorator 把「動態附加責任」這件事抽出來——不必為每種組合做子類
- 它是鏈式 + 工廠的合作;建鏈邏輯一定要與 Client 分離
- Java I/O 是教科書級的範例
- 不要把模式的「結構」誤當成模式本身——同樣的精神可以用 collection 物件等不同結構去落實