本章主軸#

裝飾者模式(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:

角色範例
來源(被裝飾者)FileInputStreamStringBufferInputStreamByteArrayInputStream
裝飾者衍生自 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 物件等不同結構去落實