今天咱們來學習一下裝飾者模式。做爲一名程序猿,相信許多人都跟我同樣,在平時寫代碼的過程當中,習慣使用繼承。可是繼承有時候會出現很是嚴重的問題,不過,沒擔憂。裝飾者模式將會給愛用繼承的咱們一個全新的設計眼界!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、裝飾者用到的設計原則:
一、多用組合,少用繼承。
二、對擴展開放,對修改關閉。