設計模式——裝飾者模式

《Head First 設計模式》 學習筆記,碼雲同步更新中

如有錯誤或不足之處,請一定指出,謝謝~

往期回顧

  1. 設計模式——策略模式
  2. 設計模式——裝飾者模式
  3. 設計模式——觀察者模式

裝飾者模式

  • 定義:
    • 動態地將責任附加到對象身上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
  • 特點:
    • 裝飾者和被裝飾着有共同的超類,裝飾過的對象可以替代原始對象使用
    • 可以用一個或多個裝飾者包裝一個對象
    • 裝飾者可以在所委託被裝飾者的行爲之前、之後,加上自己的行爲,來達到特定目的
  • 注意:
    • 這裏用到繼承的目的不是“繼承行爲”,而是“類型匹配”。對象的行爲來自於對象的組合。
      這並不違反之前提到的設計原則。
  • 優點:
    • 可以很靈活地擴展對象功能,擴展時符合“開閉原則”
  • 缺點:
    • 會產生很多對象,增加系統複雜度,加大學習理解成本
    • 使用時更容易出錯,錯誤排查也更加困難(但結合工廠模式和生成器模式後會得到很大改善)
  • 案例
    • 咖啡價格計算程序的最初實現:有一個咖啡的超類,所有品種的咖啡都會繼承他,並定義自己的描述和價格。
      但帶來的問題是:不僅咖啡種類很多,當加入不同的配料(奶,焦糖,奶泡,摩卡)時,又會有不一樣的價格。
      如果這樣實現,會產生茫茫多的咖啡子類。
    • 裝飾者模式改造:
      • 一杯加香草、榛子的美式咖啡的計價過程:
        1. new一個美式咖啡對象
        2. 用香草對象裝飾它
        3. 用榛子對象裝飾它
        4. 調用cost()方法,並依賴委託將配料的價格加上去
  • 代碼
/**
 * 飲料抽象超類
 **/
public abstract class Beverage {
    String description = "未知飲料";

    public String getDescription() {
        return description;
    }

    // 金額應該用BigDecimal
    public abstract double cost();
}

/**
 * 配料裝飾者超類
 * 繼承飲料類
 **/
public abstract class CondimentDecorator extends Beverage {
    /**
     * 這裏重寫的目的是約束配料類重寫獲取描述方法,
     * 最終拿到完整的描述鏈
     * 例如:“美式,香草,榛子”
     */
    @Override
    public abstract String getDescription();
}

/**
 * 美式咖啡類
 */
public class Americano extends Beverage {
    public Americano() {
        description = "美式"; // 構造方法,修改繼承自超類的description
    }

    @Override
    public double cost() {
        return 21; // 返回美式本身的價格
    }
}

/**
 * 香草配料
 **/
public class Vanilla extends CondimentDecorator {
    Beverage beverage; // 用來記錄被裝飾者

    public Vanilla(Beverage beverage) { // 構造函數,被裝飾者作爲參數
        this.beverage = beverage;
    }

    @Override
    public String getDescription() { // 獲取完整的描述
        return beverage.getDescription() + ", 香草";
    }

    @Override
    public double cost() { // 用被裝飾者的價格加上香草自己的價格
        return 3 + beverage.cost();
    }
}

/**
 * 榛子配料
 **/
public class Hazelnut extends CondimentDecorator {
    Beverage beverage;

    public Hazelnut(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 榛子";
    }

    @Override
    public double cost() {
        return 5 + beverage.cost();
    }
}

/**
 * 測試
 */
public class Test {
    public static void main(String[] args) {
        Beverage beverage = new Americano();
        System.out.println(beverage.getDescription() + " ¥" + beverage.cost());

        Beverage beverage2 = new Americano();
        // 加香草
        beverage2 = new Vanilla(beverage2);
        // 加榛子
        beverage2 = new Hazelnut(beverage2);
        System.out.println(beverage2.getDescription() + " ¥" + beverage2.cost());
    }
}

結果
    美式 ¥21.0
    美式, 香草, 榛子 ¥29.0
  • Java中的裝飾者模式
    • java.io 包中有茫茫多的類,但仔細觀察就會發現,其中很多類都是裝飾者。
      這也體現了裝飾者模式的缺點:對於不明所以的人來說,大料API看起來會很困擾。
    • 感性去的話可以自己編寫一個裝飾者來裝飾io,比如把輸入流中的所有小寫字母轉爲大寫。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章