本章能夠成爲 「給愛用繼承的人一個全新的設計眼界」。咱們即將再度探討繼承濫用的問題。並在會在本章中學到如何使用對象組合的方式,作到運行時裝飾類。爲什麼?一旦你熟悉了裝飾的技巧。你將可以在不修改任何底層代碼的狀況下,給你的(或別人的)對象賦予新的職責。java
章節開頭,咱們看到了一個屌絲大叔端着一杯星巴茲的咖啡,一臉賤樣的想着,「曾經我覺得男子漢應該用繼承梳理一切,後來我領教到運行時擴展,遠比編譯時期的繼承威力大。」設計模式
小子的學習方式是:先把星巴茲的問題看懂(業務難點),而後把章節拉到後邊,着手把代碼敲了一遍,這個時候回頭再來思考星巴茲遇到的業務難點,天然也就瞭然於胸。若是此時的你茫然無措,對這設計模式這本書有些迫不得已,不妨嘗試一下我這種笨拙的辦法?ide
好,你們都對於星巴茲這家咖啡店很是熟悉吧?我今天在「獲得」這個APP上閱讀了吳軍老師講的品味咖啡,卻是對於咖啡有了一點點認知,至少不會輕易以爲咖啡只是用來提神的苦水。若是你也想有一點點了解,不妨看看個人讀書筆記:http://www.jianshu.com/p/f9aa11449ab8(如何能夠,建議在獲得這個APP上訂閱吳軍老師的專欄吧,嘗試着另外一種生活的態度)函數
那好,說正題,咱們說一下星巴茲在中國迅速鋪開市場的時候,想要更新訂單系統時趕上的業務痛點:星巴茲目前有四種飲料,他們單獨售價都是固定好的,而此時由於業務拓展,爲了迎合市場客戶的需求,新增了幾種調料,後續可能會增長其餘的飲料種類也說不定哦。那麼,基於原有的訂單業務是怎麼實現的呢?學習
先來看原先的訂單系統的飲料類圖:優化
因此若是盲目的按照原先的繼承結構來實現,那麼星巴茲可能須要實現的類圖以下:this
這已經不是簡單的類爆炸了,若是接下來星巴茲在中國的業務持續飆升呢?整個訂單系統將龐大到沒法想象,已經能夠預料到,若是星巴茲的工程師不被逼瘋,大概也只能跑路了。由於維護近乎沒法維護的代碼,足矣讓工程師溜之大吉。spa
或許有沉迷繼承的傢伙會站出來,指着小子的鼻子說,只要在繼承的結構上優化,能夠解決這種問題:好比在Beverage基類上作處理,就能夠完美解決龐大類的問題,並且僅僅只須要五個類,不信你看看設計好的類圖:.net
乍一看,誒,彷佛真的能夠完美解決的耶?可是仔細想一想,這個繼承的結構是站在什麼角度上來考慮的?是站在既定的業務再也不擴展而設計的!好比此時要新增一種調料,是否是還要繼續修改基類?好比有一個客戶的口味很是重,他須要兩個份量的摩卡,系統怎麼實現呢?而且,假如星巴茲調查到競爭對手在買的一款綠茶在中國很是的暢銷,客戶正在源源不斷的流失,總部要求星巴茲分部必定要加上綠茶,可是綠茶的結構裏,不該該有摩卡,不該該有豆漿啊!因此,這個結構的實現並未從真正意義上解決系統痛點。若是有其餘的設計方案,咱們是否先考慮考慮軟件開發的系統設計原則?好比:設計
軟件開發的無上法典:開閉原則
關於開閉原則,這裏不加以贅述,若是不瞭解的童鞋,請猛烈戳下邊的連接:http://blog.csdn.net/zhengzhb/article/details/7296944(不是小子的筆記,可是寫的很好,雙手奉上)
既然繼承不能很好的解決問題,思考更優的解決方案以後,請跟小子一塊兒認識一下今天的主題——裝飾者模式。
前邊說道星巴茲的訂單系統擴展的問題,簡直讓人無語,聞風喪膽,那麼若是使用裝飾者模式是如何解決問題的呢?很簡單,思考——「把星巴茲提供的飲料(請自行過濾掉配料)做爲主體對象(被裝飾對象),而後把配料(裝飾對象)一步步裝配到飲料的主體對象上。」
好比: 我到了星巴茲咖啡店,點了一杯濃縮咖啡Espresso ,這個時候我想要加一些摩卡Mocha,再加一些豆漿 SoybeanMilk。因此這杯咖啡的價格應該是:Espresso + Mocha + SoybeanMilk。從一個主體Espresso裝飾上Mocha,再裝飾上SoybeanMilk,以下圖:
那麼價格如何計算?
SoybeanMilk.cost() + (Mocha.cost() + Espresson.cost())
裝飾者模式定義:動態的將責任附加到對象上,若要擴展功能,裝飾者模式提供了比繼承更加彈性更加優越的替代方案;
另外一個軟件開發無上法典:組合優先考慮於繼承。
如下是實現的代碼,注意Beverage與CondimentDecroator的關係:
package cn.org.lennon.decorate; import java.math.BigDecimal; /** * 選擇head first書中的星巴茲案例,星巴茲是一件飲料店,旗下有多種飲料。 * * @author lennon * @time 2017年3月19日上午11:35:36 * @className Beverage 飲料 */ public abstract class Beverage { /** * 飲料的價格 */ protected BigDecimal price; /** * 描述,咱們能夠稱之爲名稱 */ protected String description = "unkonw Beverage"; /** * 獲取到一種飲料的描述(好比名稱) * * @return */ public String getDescrition() { return description; } /** * 計算這杯飲料的價格 * * @return */ public abstract BigDecimal cost() ; }
package cn.org.lennon.decorate; /** * 星巴茲調料的抽象類,擴展至Berverage,由於咱們認爲調料其實也是飲料中的一種。 * * @author lennon * @time 2017年3月19日上午11:48:31 * */ public abstract class CondimentDecoratore extends Beverage { /** * 獲取調料的相關描述(咱們能夠認爲是名稱) * * @return */ public abstract String getDescription() ; }
因此,來看看被裝飾者(星巴茲咖啡店的主要飲料)如何實現吧:
package cn.org.lennon.decorate.mian; import java.math.BigDecimal; import cn.org.lennon.decorate.Beverage; /** * 濃縮咖啡,是星巴茲的一種主要飲料 * * @author lennon * @time 2017年3月19日上午11:51:19 * */ public class Espresso extends Beverage { /** * 構造函數,初始化濃縮咖啡的描述(名稱), 以及它的價格 */ public Espresso(BigDecimal price){ this.description = "Espresso"; this.price = price; } /** * 它的金額數量是 */ @Override public BigDecimal cost() { // TODO Auto-generated method stub return price; } }
那麼,看看裝飾者(星巴茲咖啡店的調料)又是如何實現的吧:
package cn.org.lennon.decorate.condiment; import java.math.BigDecimal; import cn.org.lennon.decorate.Beverage; import cn.org.lennon.decorate.CondimentDecoratore; /** * 星巴茲的調料中,有一種叫作摩卡的調料 * * @author lennon * @time 2017年3月19日上午11:55:48 * */ public class Mocha extends CondimentDecoratore { /** * 飲料 */ private Beverage beverage; /** * 構造函數,初始化摩卡的裝飾在一杯飲料之上 * * @param beverage */ public Mocha(Beverage beverage, BigDecimal price){ this.beverage = beverage; this.price = price; } @Override public String getDescription() { // TODO Auto-generated method stub return this.beverage.getDescrition() + " + Mocha"; } @Override public BigDecimal cost() { // TODO Auto-generated method stub return this.price.add(this.beverage.cost()); } } package cn.org.lennon.decorate.condiment; import java.math.BigDecimal; import cn.org.lennon.decorate.Beverage; import cn.org.lennon.decorate.CondimentDecoratore; /** * 星巴茲的調料中,有一種叫作豆漿的調料 * * @author lennon * @time 2017年3月19日下午12:04:04 * */ public class SoybeanMilk extends CondimentDecoratore { /** * 飲料 */ private Beverage beverage; /** * 構造函數,初始化奶油的裝飾在一杯飲料之上 * * @param beverage */ public SoybeanMilk(Beverage beverage, BigDecimal price){ this.beverage = beverage; this.price = price; } @Override public String getDescription() { // TODO Auto-generated method stub return this.beverage.getDescrition() + " + SoybeamMilk"; } @Override public BigDecimal cost() { // TODO Auto-generated method stub return this.price.add(this.beverage.cost()); } } package cn.org.lennon.decorate.condiment; import java.math.BigDecimal; import cn.org.lennon.decorate.Beverage; import cn.org.lennon.decorate.CondimentDecoratore; /** * 星巴茲的調料中,有一種叫作奶油的調料 * * @author lennon * @time 2017年3月19日下午12:02:21 * */ public class Cream extends CondimentDecoratore { /** * 飲料 */ private Beverage beverage; /** * 構造函數,初始化奶油的裝飾在一杯飲料之上 * * @param beverage */ public Cream(Beverage beverage, BigDecimal price){ this.beverage = beverage; this.price = price; } @Override public String getDescription() { // TODO Auto-generated method stub return beverage.getDescrition() + " + cream"; } @Override public BigDecimal cost() { // TODO Auto-generated method stub return this.price.add(this.beverage.cost()); } }
好,那如今購買一杯濃縮咖啡,還要來點摩卡,來點豆漿,系統是如何實現的吧!
package cn.org.lennon.decorate; import java.math.BigDecimal; import cn.org.lennon.decorate.condiment.Mocha; import cn.org.lennon.decorate.condiment.SoybeanMilk; import cn.org.lennon.decorate.mian.Espresso; /** * 主程序 * * @author lennon * @time 2017年3月19日下午12:14:06 * */ public class Main { public static void main(String[] args) { // TODO Auto-generated method stub // 我要購買一杯又豆漿+摩卡的濃縮咖啡 Beverage bev = new SoybeanMilk( new Mocha(new Espresso( new BigDecimal(11)), new BigDecimal(15)), new BigDecimal(10)); System.out.println("我在星巴茲的第一杯咖啡的消費是:" + bev.cost()); } }
站在裝飾者模式的角度去思考星巴茲的故事,咱們能夠看出一個良好系統的設計,就像一門偉大的藝術。裝飾者模式幾乎完美的遵循了軟件開發原則中的「開閉原則」。整個系統的業務拓展只要實現Beverage與CondimentDecorator就能夠。同時,裝飾者模式也是一個很好的「組合優先考慮於繼承」的完美案例。由於裝飾者模式正好完美契合了星巴茲趕上的問題,可是在實際的軟件開發中,咱們經常不能很是完整的照搬模式,而且在真正的使用設計模式的時候,必定要考慮好防止過分設計,從而增長軟件複雜度。
裝飾者模式從代碼來看是很是 簡單的,經過站在代碼的角度來分析從新分析星巴茲的訂單系統的故事,而後再從新回來思考裝飾者模式,必定可讓你有另外一番收穫!
歡迎指教,我是大天然的搬運工!