引子
現實世界的裝飾器模式
大家應該都吃過手抓餅,本文裝飾器模式以手抓餅為模型展開簡介
"老板,來一個手抓餅, 加個培根, 加個雞蛋,多少錢?" |
這句話會不會很耳熟,或者自己可能都說過呢?
我們看看這句話到底表達了哪些含義呢?
你應該可以看得到這兩個基本角色
1.手抓餅 核心角色 |
2.配菜(雞蛋/培根/香腸...) 裝飾器角色 |
你既然想要吃手抓餅,自然你是奔着手抓餅去的,對吧
所以,你肯定會要一個手抓餅,至少是原味的
然后可能根據你的口味或者喜好添加更多的配菜
這個行為很自然,也很正常.
如果是在代碼的世界里面,你怎么描述: 顧客 購買 手抓餅 這一行為呢?
顧客Customer 顧客有方法buy 然后有一個手抓餅HandPancake,看起來是這樣子的
那么問題來了
如何表示 加了雞蛋的手抓餅,或者加了雞蛋和培根的手抓餅呢?
一種很可能方式是把他們都當成手抓餅的不同種類,也就是使用繼承或者說實現類的形式
那么我們有多少種手抓餅呢?
原味手抓餅/加雞蛋手抓餅/加雞蛋加培根手抓餅/加雞蛋加烤腸手抓餅/加雞蛋加培根加烤腸手抓餅手抓餅/.......
很顯然,這就是數學中的組合,最終的個數跟我們到底有多少種配菜有關系
|
如果按照這種思維方式,我們將會有無數個手抓餅類,而且如果以后多了一種配菜,類的個數將會呈現爆炸式的增長
這是你想要的結果么?
在現實世界里面,你會很自然的說 "老板,來一個手抓餅, 加個培根, 加個雞蛋,多少錢?""
|
那么為什么在程序世界里面,你卻很可能說"老板,給我來一個加了雞蛋加了培根的那種手抓餅" 呢? |
手抓餅代碼示例
手抓餅接口和具體的一家店鋪提供的手抓餅
package decorator; /** * Created by noteless on 2018/9/6. * Description:手抓餅接口 描述抽象的手抓餅 */ public interface HandPancake { /** * 提供手抓餅 */ String offerHandPancake(); /**計算手抓餅的價格 * @return */ Integer calcCost(); } package decorator; /** * Created by noteless on 2018/9/6. * Description: Noteless 家的手抓餅 */ public class NotelessHandPancake implements HandPancake { /** * 提供noteless 家的手抓餅一份 */ @Override public String offerHandPancake() { return " noteless 家的手抓餅"; } /**計算 noteless 家 一份手抓餅的價格 * @return */ @Override public Integer calcCost() { return 3; } }
配菜抽象類(裝飾器)
package decorator; /** * Created by noteless on 2018/9/6. * Description:裝飾器類實現了手抓餅接口,具有了手抓餅的類型 */ public abstract class Decorator implements HandPancake{ private HandPancake handPancake; Decorator(HandPancake handPancake){ this.handPancake = handPancake; } /**提供手抓餅 * @return */ @Override public String offerHandPancake() { return handPancake.offerHandPancake(); } /**提供手抓餅的價格 * @return */ @Override public Integer calcCost() { return handPancake.calcCost(); } }
具體的配菜(具體的裝飾)
package decorator; /** * Created by noteless on 2018/9/6. * Description:培根 */ public class Bacon extends Decorator { Bacon(HandPancake handPancake){ super(handPancake); } @Override public String offerHandPancake() { return super.offerHandPancake()+" 加培根"; } @Override public Integer calcCost() { return super.calcCost()+4; } } package decorator; /** * Created by noteless on 2018/9/6. * Description:雞蛋 */ public class Egg extends Decorator { Egg(HandPancake handPancake){ super(handPancake); } @Override public String offerHandPancake() { return super.offerHandPancake()+"加雞蛋"; } @Override public Integer calcCost() { return super.calcCost()+2; } } package decorator; /** * Created by noteless on 2018/9/6. * Description:烤腸 */ public class Sausage extends Decorator { Sausage(HandPancake handPancake){ super(handPancake); } @Override public String offerHandPancake() { return super.offerHandPancake()+" 加香腸"; } @Override public Integer calcCost() { return super.calcCost()+3; } } package decorator; /** * Created by noteless on 2018/9/6. * Description:青菜 */ public class Vegetable extends Decorator { Vegetable(HandPancake handPancake){ super(handPancake); } @Override public String offerHandPancake() { return super.offerHandPancake()+" 加青菜"; } @Override public Integer calcCost() { return super.calcCost()+1; } }
顧客
package decorator; /** * Created by noteless on 2018/9/6. * Description:顧客具有名字,然后購買手抓餅 */ public class Customer { private String name; Customer(String name){ this.name = name; } public void buy(HandPancake handPancake){ System.out.println(name+"購買了 : "+handPancake.offerHandPancake()+ " 一份, 花了 : "+handPancake.calcCost()+"塊錢~"); System.out.println(); } }
測試類
package decorator; /** * Created by noteless on 2018/9/6. * Description: * 手抓餅3塊 * Sausage 烤腸 3塊 * Bacon 培根 4塊 * Egg 雞蛋2塊 * Vegetable 青菜 1塊 */ public class Test { public static void main(String ...strings){ //有一個顧客張三,他想吃手抓餅了,來了一個原味的 Customer customerA = new Customer("張三"); customerA.buy(new NotelessHandPancake()); //有一個顧客李四,他想吃手抓餅了,他加了一根烤腸 Customer customerB = new Customer("李四"); customerB.buy(new Sausage(new NotelessHandPancake())); //有一個顧客王五,他想吃手抓餅了,他加了一根烤腸 又加了培根 Customer customerC = new Customer("王五"); customerC.buy(new Bacon(new Sausage(new NotelessHandPancake()))); //有一個顧客王五的兄弟,他想吃手抓餅了,他加了培根 又加了烤腸 Customer customerC1 = new Customer("王五的兄弟"); customerC1.buy(new Sausage(new Bacon(new NotelessHandPancake()))); //有一個顧客趙六,他想吃手抓餅了,他加了一根烤腸 又加了2份培根 Customer customerD = new Customer("趙六"); customerD.buy(new Bacon(new Bacon(new Sausage(new NotelessHandPancake()))));
//有一個顧客 王二麻子,他想吃手抓餅了,特別喜歡吃青菜 來了三分青菜 Customer customerE = new Customer("王二麻子"); customerE.buy(new Vegetable(new Vegetable(new Vegetable(new NotelessHandPancake()))));
//有一個顧客 有錢人 王大富 來了一個全套的手抓餅 Customer customerF = new Customer("王大富"); customerF.buy(new Egg(new Vegetable(new Bacon(new Sausage(new NotelessHandPancake()))))); } }
我們有一個顧客Customer類,他擁有buy方法,可以購買手抓餅
手抓餅接口為 HandPancake 具體的手抓餅為NotelessHandPancake
然后提供了一個配菜類,這個配菜類的行為和手抓餅是一致的,在提供手抓餅的同時還能夠增加一些額外的
然后還有四個具體的配菜 培根 香腸 雞蛋 青菜
運行測試類,會算賬的親們,看看單價是否還對的上?
UML圖
懶得畫了,IDEA自動生成的
手抓餅裝飾器模式中的根本
上面的代碼還是比較清晰的,如果你沒辦法仔細看進去的話,我們換一種思維方式來思考手抓餅的裝飾器模式
你可以這么理解:
你過去手抓餅的攤位那邊,你說老板來一個手抓餅,加培根,加雞蛋
攤主那邊是這樣子的:
老板負責直接做手抓餅
旁邊站着漂亮的老板娘,手里拿着手抓餅的袋子,負責幫你裝袋,你總不能直接用手拿餅,對吧
接下來我們說下過程:
老板馬上就開始做手抓餅了,做好了之后,老板把手抓餅交給了旁邊站着的老板娘
老板娘在給裝袋並且交給你之前
把雞蛋和培根放到了你的手抓餅里面
然后又放到了包裝袋子里面
接着遞給了你
你說到底是老板娘手里包裝好的手抓餅是手抓餅 還是老板做好的熱氣騰騰的是手抓餅呢?
其實,老板做好的熱氣騰騰的手抓餅,正是我們上面提供出來的具體的手抓餅
老板娘手里拿着的手抓餅包裝袋來包裝手抓餅,也是手抓餅,只不過是包裝了下,這個就是裝飾器的概念
所以裝飾器模式還有一個名字 包裝器模式(Wrapper)
|
解決問題的根本思路是使用組合替代了繼承
上面我們也進行了分析,繼承會出現類的個數的爆炸式增長
組合,不僅僅動態擴展了類的功能,而且還很大程度上減少了類的個數
不過顯然,如果你的裝飾類過多,雖說比繼承好很多,但是問題還是一樣的,都會類過多
根本: 是你還有你 |
我們上面的類的結構中,裝飾器包含一個手抓餅對象作為屬性,他也實現了手抓餅接口
所以我們說,是你還有你
每次自己返回結果之前,都還會調用自己含有的對象的方法
看下調用流程, 你說它的形式跟
遞歸調用有什么區別?
面向對象中的適配器模式詳解
意圖
動態的給一個對象添加額外的職責,簡單說,動態的擴展職責 就增加功能來說,裝飾器模式比生成子類要更加靈活 所以裝飾器模式主要解決繼承子類爆炸增長的問題 |
裝飾器模式中的角色
Component | 抽象構建 | 裝飾器模式中必然有一個最基本最原始的-> 接口/抽象類 來充當抽象構建 |
抽象的手抓餅 HandPancake |
ConcreteComponent | 具體構建 |
是抽象構建的一個具體實現
你要裝飾的就是它
|
具體某家店鋪生產的手抓餅 NotelessHandPancake |
Decorator | 裝飾抽象類 | 一般是一個抽象類 實現抽象構建 並且必然有一個private變量指向Component 抽象構建 |
配菜抽象類(裝飾器) Decorator |
ConcreteDecorator | 具體的裝飾類 | 必須要有具體的裝飾角色 否則裝飾模式就毫無意義了 |
具體的配菜(具體的裝飾) Bacon Egg Vegetable Sausage |
仔細體味下<是你 還有你>
Decorator 是Component 還有Component
|
OOP中的一個重要設計原則
類應該對擴展開放,對修改關閉 |
所謂修改就是指繼承,一旦繼承,那么將會對部分源代碼具有修改的能力,比如覆蓋方法,所以你盡量不要做這件事情 擴展就是指的組合,組合不會改變任何已有代碼,動態得擴展功能 |
裝飾器模式優點
裝飾器模式缺點
裝飾器模式雖然從數量級上減少了類的數量,但是為了要裝飾,仍舊會增加很多的小類 這些具體的裝飾類的邏輯將不會非常的清晰,不夠直觀,容易令人迷惑 |
裝飾器模式雖然減少了類的爆炸,但是在使用的時候,你就可能需要更多的對象來表示繼承關系中的一個對象 |
多層的裝飾是比較復雜,比如查找問題時,被層層嵌套,不容易發現問題所在 |
裝飾器模式使用場景
當你想要給一個類增加功能,然而,卻並不想修改原來類的代碼時,可以考慮裝飾器模式 如果你想要動態的給一個類增加功能,並且這個功能你還希望可以動態的撤銷,就好像直接拿掉了一層裝飾物 |
裝飾器模式的簡化變形
裝飾器模式是對繼承的一種強有力的補充與替代方案,裝飾器模式具有良好的擴展性
再次強調,設計模式是一種思維模式,沒有固定公式
如果需要的話,可以進行簡化
設計模式是作為解決問題或者設計類層級結構時的一種思維的存在,而不是公式一樣的存在!
|