天天看點

Head First 設計模式 —— 03. 裝飾器 (Decorator) 模式

思考題

有如下類設計:

Head First 設計模式 —— 03. 裝飾器 (Decorator) 模式
Head First 設計模式 —— 03. 裝飾器 (Decorator) 模式

如果牛奶的價錢上揚,怎麼辦?新增一種焦糖調料風味時,怎麼辦?

造成這種維護上的困難,違反了我們之前提過的哪種設計原則?

P82

  • 取出并封裝變化的部分,讓其他部分不收影響
  • 多用組合,少用繼承

請為下面類的 cost() 方法書寫代碼。

P83

抽象類:Beverage

public class Beverage {
    public double cost() {
        double totalCost = 0.0;

        if (hasMilk()) {
            totalCost += milkCost;
        }
        if (hasSoy()) {
            totalCost += soyCost;
        }
        if (hasMocha()) {
            totalCost += mochaCost;
        }
        if (hasWhip()) {
            totalCost += whipCost;
        }

        return totalCost;
    }
}           

具體類:DarkRoast

public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "Most Excellent Dark Roast";
    }

    public double cost() {
        return baseCost + super.cost();
    }
}           

當哪些需求或因素改變時會影響這個設計?

P84

  • 調料價錢的改變會使我們改變現有代碼
  • 一旦出現新的調料,我們就需要加上新的方法,并改變超類中的 cost() 方法
  • 以後可能會開發出新飲料。對這些飲料而言(例如:冰茶),某些調料可能并不适合,但是在這個設計方式中,Tea (茶)子類仍然将繼承那些不适合的方法,例如:hasWhip() (加奶泡)
  • 萬一顧客想要雙倍摩卡或咖啡,怎麼辦?
  • 調料價錢随着具體飲料而改變
  • 飲料基礎價錢随着大中小被的不同而改變

設計原則

  • 開閉原則:類應該對擴充開放,對修改關閉

    P86

    • 政策模式、觀察者模式和裝飾器模式均遵循開閉原則

      P105

裝飾器模式

動态地将責任附加到對象上,而不改變其原有代碼。若擴充功能,裝飾器提供了比繼承更優彈性的替代方案。

P91

Head First 設計模式 —— 03. 裝飾器 (Decorator) 模式
特點
  • 裝飾類和被裝飾類有相同的超類型

    P90

  • 裝飾類可以在所委托的被裝飾類的行為之前(或之後),加上自己的行為,以達到特定的目的

    P90

  • 可以透明地插入裝飾器,使用時甚至不需要知道是在和裝飾器互動

    P104

  • 适合用來建立有彈性的設計,維持開閉原則

    P104

    缺點
  • 存在大量小類,使用時将會增加代碼複雜度

    P101

    P104

  • 使用時依賴某種特殊類型,然後忽然導入裝飾器,卻又沒有周詳地考慮一切

    P104

我們在星巴茲的朋友決定開始在菜單上加上咖啡的容量大小,供顧客可以選擇小貝(tall)、中杯(grande)、大杯(venti)。星巴茲認為這是任何咖啡都必須具備的,是以在 Beverage 類中加上了 getSize() 與 setSize() 。他們也希望調料根據咖啡容量收費,例如:小中大杯的咖啡加上豆漿,分别加收 0.10、0.15、0.20 美金。

如何改變裝飾者類應對這一的需求?

P99

public class Soy extends CondimentDecorator {
    Beverage beverage;

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

    public int getSize() {
        return beverage.getSize();
    }

    public void setSize(int size) {
        beverage.setSize(size);
    }

    public String getDescription() {
        return beverage.getDescription() + ", Soy";
    }

    public double cost() {
        double soyCost = 0.0;

        switch (getSize()) {
            case TALL:
                soyCost = 0.10; 
                break;
            case GRANDE:
                soyCost = 0.15; 
                break;
            case VENTI:
                soyCost = 0.20; 
                break;
            default:
                soyCost = 0.0;
        }

        return beverage.cost() + soyCost;
    }
}           

所思所想

  • 裝飾器模式使用了繼承(或實作接口)的方式,是以超類型增加方法時,所有子類都需要改變,設計時要充分考慮
  • 總感覺裝飾器模式很熟悉,看了 java-design-patterns/decorator 後,發現裝飾器模式的思想平常都是運用在 AOP 中實作的 (最後學了代理模式發現原來是動态代理模式)
    本文首發于公衆号:滿賦諸機( 點選檢視原文 ) 開源在 GitHub : reading-notes/head-first-design-patterns
    Head First 設計模式 —— 03. 裝飾器 (Decorator) 模式

繼續閱讀