裝飾器模式 Decorator 結構型 設計模式 (十)

引子

 
image_5b91e046_4d0a
 
image_5b91e046_bbf
 
 

 

現實世界的裝飾器模式

你們應該都吃過手抓餅,本文裝飾器模式以手抓餅爲模型展開簡介
"老闆,來一個手抓餅,  加個培根,  加個雞蛋,多少錢?"
這句話會不會很耳熟,或者本身可能都說過呢?
 
咱們看看這句話到底表達了哪些含義呢?
你應該能夠看獲得這兩個基本角色
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
而後提供了一個配菜類,這個配菜類的行爲和手抓餅是一致的,在提供手抓餅的同時還可以增長一些額外的
而後還有四個具體的配菜 培根 香腸 雞蛋 青菜
 
運行測試類,會算帳的親們,看看單價是否還對的上?
image_5b91e046_7b49

UML圖

懶得畫了,IDEA自動生成的
image_5b91e046_739c
 

 手抓餅裝飾器模式中的根本

上面的代碼仍是比較清晰的,若是你沒辦法仔細看進去的話,咱們換一種思惟方式來思考手抓餅的裝飾器模式
 
你能夠這麼理解:
你過去手抓餅的攤位那邊,你說老闆來一個手抓餅,加培根,加雞蛋
 
攤主那邊是這樣子的:
老闆負責直接作手抓餅
旁邊站着漂亮的老闆娘,手裏拿着手抓餅的袋子,負責幫你裝袋,你總不能直接用手拿餅,對吧
 
接下來咱們說下過程:
老闆立刻就開始作手抓餅了,作好了以後,老闆把手抓餅交給了旁邊站着的老闆娘
老闆娘在給裝袋而且交給你以前
把雞蛋和培根放到了你的手抓餅裏面
而後又放到了包裝袋子裏面
接着遞給了你
 
你說究竟是老闆娘手裏包裝好的手抓餅是手抓餅  仍是老闆作好的熱氣騰騰的是手抓餅呢?
 
其實,老闆作好的熱氣騰騰的手抓餅,正是咱們上面提供出來的具體的手抓餅
老闆娘手裏拿着的手抓餅包裝袋來包裝手抓餅,也是手抓餅,只不過是包裝了下,這個就是裝飾器的概念
 
因此裝飾器模式還有一個名字  包裝器模式(Wrapper)
 
 
解決問題的根本思路是使用組合替代了繼承
上面咱們也進行了分析,繼承會出現類的個數的爆炸式增加
組合,不只僅動態擴展了類的功能,並且還很大程度上減小了類的個數
不過顯然,若是你的裝飾類過多,雖然說比繼承好不少,可是問題仍是同樣的,都會類過多
 
根本:  是你還有你
 
咱們上面的類的結構中,裝飾器包含一個手抓餅對象做爲屬性,他也實現了手抓餅接口
因此咱們說,是你還有你
每次本身返回結果以前,都還會調用本身含有的對象的方法
 
看下調用流程, 你說它的形式跟 遞歸調用有什麼區別?
image_5b91e047_17fb
 
 

面向對象中的適配器模式詳解

意圖

動態的給一個對象添加額外的職責,簡單說,動態的擴展職責
就增長功能來講,裝飾器模式比生成子類要更加靈活
因此裝飾器模式主要解決繼承子類爆炸增加的問題
 

裝飾器模式中的角色

Component 抽象構建 裝飾器模式中必然有一個最基本最原始的->
接口/抽象類
來充當抽象構建
抽象的手抓餅    HandPancake
ConcreteComponent 具體構建 
是抽象構建的一個具體實現
你要裝飾的就是它
具體某家店鋪生產的手抓餅   NotelessHandPancake
Decorator 裝飾抽象類 通常是一個抽象類
實現抽象構建
而且必然有一個private變量指向Component 抽象構建
配菜抽象類(裝飾器)   Decorator
ConcreteDecorator 具體的裝飾類 必需要有具體的裝飾角色
不然裝飾模式就毫無心義了
具體的配菜(具體的裝飾)    Bacon Egg  Vegetable Sausage
 
image_5b91e047_1c47
仔細體味下<是你 還有你>
Decorator 是Component 還有Component
 
OOP中的一個重要設計原則
類應該對擴展開放,對修改關閉
所謂修改就是指繼承,一旦繼承,那麼將會對部分源代碼具備修改的能力,好比覆蓋方法,因此你儘可能不要作這件事情
擴展就是指的組合,組合不會改變任何已有代碼,動態得擴展功能
 

裝飾器模式優勢

裝飾類和被裝飾類能夠獨立發展,而不會相互耦合
 
Component類無須知道Decorator類,Decorator類是從外部來擴展Component類的功能,
而Decorator也不用知道具體的構件
裝飾模式是繼承關係的一個替代方案
咱們看裝飾類Decorator,無論裝飾多少層,他始終是一個Component,實現的仍是is-a的關係,因此他是繼承的一種良好替代方案
若是設計得當,裝飾器類的嵌套順序能夠任意,好比
image_5b91e047_2ed8
必定要注意前提,那就是你的裝飾不依賴順序

裝飾器模式缺點

裝飾器模式雖然從數量級上減小了類的數量,可是爲了要裝飾,仍舊會增長不少的小類
這些具體的裝飾類的邏輯將不會很是的清晰,不夠直觀,容易使人迷惑
裝飾器模式雖然減小了類的爆炸,可是在使用的時候,你就可能須要更多的對象來表示繼承關係中的一個對象
多層的裝飾是比較複雜,好比查找問題時,被層層嵌套,不容易發現問題所在
 

裝飾器模式使用場景

當你想要給一個類增長功能,然而,卻並不想修改原來類的代碼時,能夠考慮裝飾器模式
若是你想要動態的給一個類增長功能,而且這個功能你還但願能夠動態的撤銷,就好像直接拿掉了一層裝飾物

裝飾器模式的簡化變形

裝飾器模式是對繼承的一種強有力的補充與替代方案,裝飾器模式具備良好的擴展性
再次強調,設計模式是一種思惟模式,沒有固定公式
若是須要的話,能夠進行簡化
若是省略抽象構建,裝飾器直接裝飾一個類的話,
那麼能夠裝飾器直接繼承這個類

image_5b91e047_7db9
若是隻有一個具體的裝飾器類,那麼能夠省略掉 Decorator
ConcreteDecorator 充當了ConcreteDecorator 和 Decorator的角色
image_5b91e047_e8e 
 
 
設計模式是做爲解決問題或者設計類層級結構時的一種思惟的存在,而不是公式同樣的存在!
相關文章
相關標籤/搜索