裝飾者(decorator,又稱 wrapper):把既有物件「包」一層、擴充其功能。

裝飾者提供與底層物件相似或相同的 API,方法內部則委派給底層物件。

裝飾者模式天生鼓勵跨層的 API 重複。

範例#

  • Java I/OBufferedInputStreamInputStream 的裝飾者
    • 介面相同,但加入緩衝
    • 呼叫 read 讀單一字元時,內部呼叫底層 read 讀一大塊,再把多餘字元留待後續呼叫使用
  • 視窗系統ScrollableWindow 裝飾 Window,加上水平與垂直捲軸

設計動機與陷阱#

裝飾者的初衷:把專用擴充與通用核心分開。

但裝飾者類別往往很淺:為了一點點新功能,引入大量樣板程式碼,而且常含許多穿透型方法。

過度使用裝飾者,會像 Java I/O 一樣爆出大量淺類別

建立 decorator 前的替代方案#

在建立 decorator 之前,先想想下列替代方案:

  1. 直接加進底層類別

    • 適用情境:新功能本身相當通用、邏輯上與底層類別密切相關,或多數使用者反正都會用到
    • 例如:幾乎每位 InputStream 使用者都會建 BufferedInputStream,緩衝又是 I/O 的自然部分 → 兩者就應該合併
  2. 與特定使用情境合併

    • 若新功能是為某個特定情境特化的 → 不如併入該情境,不要做成獨立類別
  3. 與既有 decorator 合併

    • 與其多開一個淺 decorator,不如把功能塞進現有 decorator → 變成一個更深的類別
  4. 獨立的 stand-alone 類別

    • 問問自己:新功能真的需要包覆既有功能嗎?
    • 例如視窗例子中,捲軸或許可以實作為獨立元件,不必包覆整個 Window

有時 decorator 確實合理——但通常有更好的替代方案