設計模式之裝飾者模式

  今天咱們來學習一下裝飾者模式。做爲一名程序猿,相信許多人都跟我同樣,在平時寫代碼的過程當中,習慣使用繼承。可是繼承有時候會出現很是嚴重的問題,不過,沒擔憂。裝飾者模式將會給愛用繼承的咱們一個全新的設計眼界!java

1、星巴茲咖啡的故事ide

  咱們經過一個生動有趣的例子來引出咱們今天的主角--裝飾者模式。學習

  一、如今呢,有一個咖啡館,它有一套本身的訂單系統,當顧客來咖啡館的時候,能夠經過訂單系統來點本身想要的咖啡。他們原先的設計是這樣子的:ui

 

 

   二、此時、咖啡館爲了吸引更多的顧客,須要在訂單系統中容許顧客選擇加入不一樣調料的咖啡,例如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha,也就是巧克力風味)或覆蓋奶泡。星巴茲會根據所加入的調料收取不一樣的費用。因此訂單系統必須考慮到這些調料部分。this

  下面是他們的第一次嘗試:spa

 

 

   這種設計確定是不行的,簡直分分鐘把人逼瘋的節奏,有木有!設計

  三、這時,有我的提出了新的方案,利用實例變量和繼承,來追蹤這些調料。code

    具體爲:先從Beverage基類下手,加上實例變量表明是否加上調料(牛奶、豆漿、摩卡、奶泡……),對象

  

 

 

 

 

 

   這種設計雖然知足瞭如今的需求,可是咱們想一下,若是出現下面狀況,咱們怎麼辦,blog

    ①、調料價錢的改變會使咱們更改現有代碼。

    ②、一旦出現新的調料,咱們就須要加上新的方法,並改變超類中的cost()方法。

    ③、之後可能會開發出新飲料。對這些飲料而言(例如:冰茶),某些調料可能並不適合,可是在這個設計方式中,Tea(茶)子類仍將繼承那些不適合的方法,例如:hasWhip()(加奶泡)。

    ④、萬一顧客想要雙倍摩卡咖啡,怎麼辦?

  很明顯,上面的設計並不可以從根本上解決咱們所碰到的問題。而且這種設計違反了 開放關閉原則(類應該對擴展開放,對修改關閉。)

  那咱們怎麼辦呢?好啦,裝飾者能夠很是完美的解決以上的全部問題,讓咱們有一個設計很是nice的咖啡館。

2、定義

  裝飾者模式:動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

3、實現

  下面咱們就利用裝飾者模式來實現一個全新的咖啡館。

  一、咱們要以飲料爲主體,而後在運行時以調料來「裝飾」(decorate)飲料。比方說,若是顧客想要摩卡和奶泡深焙咖啡,那麼,要作的是:

    ①、拿一個深焙咖啡(DarkRoast)對象

    ②、以摩卡(Mocha)對象裝飾它

    ③、以奶泡(Whip)對象裝飾它

    ④、調用cost()方法,並依賴委託(delegate)將調料的價錢加上去。

  好了!可是如何「裝飾」一個對象,而「委託」又要如何與此搭配使用呢?那就是把裝飾者對象當成「包裝者」。讓咱們看看這是如何工做的:

 

 

 

 

 

 

 

 

   二、設計

  咱們將咱們所知道的寫下來:

    ①、裝飾者和被裝飾對象有相同的超類型。

    ②、你能夠用一個或多個裝飾者包裝一個對象。

    ③、既然裝飾者和被裝飾對象有相同的超類型,因此在任何須要原始對象(被包裝的)的場合,能夠用裝飾過的對象代替它。

    ④、裝飾者能夠在所委託被裝飾者的行爲以前與 / 或以後,加上本身的行爲,以達到特定的目的。

    ⑤、對象能夠在任什麼時候候被裝飾,因此能夠在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象。

  下面,咱們來看一下裝飾者模式的類圖:

 

 

  利用裝飾者模式來實現咱們的訂單系統的類圖:

 

 

   咱們已經將訂單系統設計完畢,下面讓咱們用代碼來實現它吧!

  三、代碼實現:

    飲料抽象類:

/**
 * @author fan_rc@suixingpay.com
 * @description 飲料抽象類
 * @date 2019/9/17 20:53
 */
public abstract class Beverage {

    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    /**
     * cost方法是用來返回飲料的價錢(需在具體類中本身實現)
     *
     * @return
     */
    public abstract BigDecimal cost();
}

    深焙咖啡類:

/**
 * 深焙咖啡類(一種具體的飲料)
 */
public class DarkRoast extends Beverage {

    /**
     * 說明他是DarkRoast飲料
     */
    public DarkRoast() {
        description = "DarkRoast";
    }

    /**
     * 實現cost方法,用來返回DarkRoast(深焙咖啡)的價格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("3.00");
    }
}

    低咖啡因咖啡類:

/**
 * 低咖啡因咖啡類(一種具體的飲料)
 */
public class Decaf extends Beverage {

    /**
     * 說明他是Decaf飲料
     */
    public Decaf() {
        description = "Decaf";
    }

    /**
     * 實現cost方法,用來返回Decaf(低咖啡因咖啡)的價格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("4.00");
    }
}

    濃縮咖啡類:

/**
 * 濃縮咖啡類(一種具體飲料)
 */
public class Espresso extends Beverage {

    /**
     * 說明他是Espresso飲料
     */
    public Espresso() {
        description = "Espresso";
    }

    /**
     * 實現cost方法,用來返回Espresso(濃縮咖啡)的價格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("2.00");
    }
}

    調料裝飾着抽象類:

/**
 * @author fan_rc@suixingpay.com
 * @description 調料裝飾着抽象類(繼承自飲料抽象類)
 * @date 2019/9/17 20:56
 */
public abstract class CondimentDecorator extends Beverage {

    /**
     * 全部的調料裝飾者都必須從新實現getDescription()方法
     * 這樣纔可以用遞歸的方式來獲得所選飲料的總體描述
     *
     * @return
     */
    public abstract String getDescription();
}

    摩卡調料類:

/**
 * 摩卡調料類(繼承自CondimentDecorator)
 */
public class Mocha extends CondimentDecorator {

    /**
     * 用一個實例變量記錄飲料,也就是被裝飾者
     */
    Beverage beverage;

    /**
     * 構造器初始化飲料變量
     *
     * @param beverage
     */
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原來飲料的基礎上添加上Mocha描述(原來的飲料加入Mocha調料,被Mocha調料裝飾)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Mocha";
    }

    /**
     * 在原來飲料的基礎上加上Mocha的價格(原來的飲料加入Mocha調料,被Mocha調料裝飾)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.2").add(beverage.cost());
    }
}

  豆漿調料類:

/**
 * 豆漿調料類(繼承自CondimentDecorator))
 */
public class Soy extends CondimentDecorator {

    /**
     * 用一個實例變量記錄飲料,也就是被裝飾者
     */
    Beverage beverage;

    /**
     * 構造器初始化飲料變量
     *
     * @param beverage
     */
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原來飲料的基礎上添加上Soy描述(原來的飲料加入Soy調料,被Soy調料裝飾)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Soy";
    }

    /**
     * 在原來飲料的基礎上加上Soy的價格(原來的飲料加入Soy調料,被Soy調料裝飾)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.3").add(beverage.cost());
    }
}

    奶泡調料類:

/**
 * 奶泡調料類(繼承自CondimentDecorator)
 */
public class Whip extends CondimentDecorator {

    /**
     * 用一個實例變量記錄飲料,也就是被裝飾者
     */
    Beverage beverage;

    /**
     * 構造器初始化飲料變量
     *
     * @param beverage
     */
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原來飲料的基礎上添加上Whip描述(原來的飲料加入Whip調料,被Whip調料裝飾)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Whip";
    }

    /**
     * 在原來飲料的基礎上加上Whip的價格(原來的飲料加入Whip調料,被Whip調料裝飾)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.4").add(beverage.cost());
    }
}

    咖啡館(模擬顧客下單):

/**
 * 咖啡館(供應咖啡)
 */
public class StarbuzzCoffee {

    public static void main(String[] args) {
        //訂一杯Espresso(2.00),不須要調料,打印出它的描述與價錢。
        Beverage beverage = new Espresso();
        System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());

        //製造出一個DarkRoast(3.00)對象,用Mocha(0.2)裝飾它,用第二個Mocha(0.2)裝飾它,用Whip(0.4)裝飾它,打印出它的描述與價錢。
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());

        //再來一杯調料爲豆漿(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述與價錢。
        Beverage beverage3 = new Decaf();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
    }
}

    咱們能夠看到運行結果:

 

  從以上,咱們能夠知道,當咱們使用繼承,致使子類膨脹,咱們不想增長不少子類的狀況下,將具體功能職責劃分,同時繼承裝飾者超類,動態地給一個對象添加一些額外的職責便實現了咱們的裝飾者模式。

4、優缺點

  一、優勢:裝飾類和被裝飾類能夠獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式能夠動態擴展一個實現類的功能。

  二、缺點:多層裝飾比較複雜。

5、使用場景: 

  一、擴展一個類的功能。

   二、動態增長功能,動態撤銷。

  實際使用:這裏咱們說一下,在java中I/O便使用了裝飾者模式。

 

 6、裝飾者用到的設計原則:

  一、多用組合,少用繼承。

  二、對擴展開放,對修改關閉。

相關文章
相關標籤/搜索