cover

想幫一杯美式咖啡加牛奶再加糖,你會寫一個 AmericanoWithMilkAndSugar class 嗎?那如果還要加鮮奶油呢?再來一個 AmericanoWithMilkAndSugarAndCream

這就是繼承的噩夢——而 Decorator 是解藥。

先講結論

Decorator Pattern 的核心思想:用包裝取代繼承。你有一個基礎物件,想加功能?包一層。再加功能?再包一層。每一層都不改原本的 code,但組合起來就能做到任何你想要的效果。

classDiagram
    class Component {
        <<interface>>
        +cost()* number
        +description()* String
    }
    class BasicCoffee {
        +cost() number
        +description() String
    }
    class Decorator {
        -coffee : Component
        +cost() number
        +description() String
    }
    class MilkDecorator {
        +cost() number
    }
    class SugarDecorator {
        +cost() number
    }
    Component <|.. BasicCoffee
    Component <|.. Decorator
    Decorator <|-- MilkDecorator
    Decorator <|-- SugarDecorator
    Decorator --> Component

經典範例:咖啡點餐系統

class BasicCoffee {
    cost() { return 50; }
    description() { return '美式咖啡'; }
}
 
class Decorator {
    constructor(coffee) { this.coffee = coffee; }
    cost() { return this.coffee.cost(); }
    description() { return this.coffee.description(); }
}
 
class MilkDecorator extends Decorator {
    cost() { return super.cost() + 20; }
    description() { return `${super.description()} + 牛奶`; }
}
 
class SugarDecorator extends Decorator {
    cost() { return super.cost() + 10; }
    description() { return `${super.description()} + 糖`; }
}
 
let coffee = new BasicCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
 
console.log(coffee.description()); // 美式咖啡 + 牛奶 + 糖
console.log(coffee.cost());        // 80

每個 Decorator 都只知道「我包住的那杯東西」和「我要加什麼」。想加鮮奶油?寫一個 CreamDecorator,不用動任何既有程式碼。

真實世界在哪用?

你可能已經用過了:

  • Express/Koa middleware:每個 middleware 就是一個 Decorator,包在 request 處理流程外面
  • TypeScript/Python 的 @decorator 語法:直接語言層級支援
  • Java I/O Streamnew BufferedReader(new InputStreamReader(new FileInputStream("file"))) ——標準的三層 Decorator

Decorator vs 繼承

用繼承的話,M 種配料的組合就是 2^M 個 class。用 Decorator 的話,M 種配料就是 M 個 Decorator class,隨意組合。

不過 Decorator 也不是萬能的——疊太多層的時候 debug 會很痛苦,因為你要一層一層剝開才知道哪層出問題。三層以內通常沒問題,超過五層就該重新思考架構了。


Decorator Pattern 就像幫手機貼保護貼、裝殼、掛吊飾——手機本身沒變,但功能和外觀都不一樣了。只是你疊了太多層以後,手機就變成一塊磚頭了


延伸閱讀