設計模式 | 用實際案例詳解裝飾者模式

一、星巴克訂單案例

有下面的需求:

  • 咖啡種類/單品咖啡:Espresso(意大利濃咖啡)利朵、ShortBlack律想、LongBlack(美式咖啡)、Decaf(無因咖啡)
  • 調料:Milk绍弟、Soy(豆?jié){)技即、Chocolate
  • 要求在擴展新的咖啡種類時,具有良好的擴展性晌柬、改動方便姥份、維護方便
  • 使用OO來計算不同種類咖啡的費用:客戶可以點單品咖啡,也可以單品咖啡+調料組合

二年碘、咖啡案例的第一個方案

設計一個Drink抽象類,表示飲料展鸡,然后des就是對咖啡的描述屿衅,比如咖啡的名字。cost()方法就是計算費用莹弊,在Drink類中做成一個抽象方法涤久。Decaf就是單品咖啡涡尘,繼承Drink,并實現(xiàn)cost()响迂,Espress&&Milk就是單品咖啡+調料考抄,這個組合很多,詳情看下圖:



這樣設計的話有一個很大的問題就是蔗彤,當我們增加一個單品咖啡川梅,或者一個新的調料,類的數(shù)量就會倍增然遏,就會出現(xiàn)類爆炸贫途。

三、咖啡案例的第二個方案

前面分析到方案1因為咖啡單品+調料組合會造成類的倍增待侵,因此可以做改進丢早,將調料內置到Drink類,這樣就不會造成類數(shù)量過多秧倾。從而提高項目的維護性怨酝,如下圖所示:


但是這個方案也有問題,比如在增加或刪除調料種類時那先,代碼的維護量很大农猬。
對于這個案例我們可以使用裝飾者模式來進行改進。

四胃榕、裝飾者模式

1盛险、裝飾者模式的定義

  • 裝飾者模式就是動態(tài)地將新功能附加到對象上。在對象功能擴展方面勋又,它比繼承更有彈性苦掘,裝飾者模式也體現(xiàn)了開閉原則。

2楔壤、裝飾者模式的原理

裝飾者模式就像打包一個快遞

  • 主體(被裝飾者)比如:陶瓷鹤啡、衣服
  • 包裝(裝飾者)比如:報紙?zhí)畛洹⑺芰吓菽紫⒓埌宓莨濉⒛景?/li>

如下圖所示:


  • Component:主體,比如類似咖啡案例中的Drink
  • ConcreteComponent和Decorator
    ConcreteComponent:具體的主題隙畜,比如前面的各個單品咖啡
    Decorator:裝飾者抖部,比如各調料
  • 在圖中的Component與ConcreteComponent之間,如果ConcreteComponent類很多议惰,還可以設計一個緩沖層慎颗,將總有的部分提取出來,抽象層是一個類。

五俯萎、用裝飾者模式實現(xiàn)咖啡案例

1傲宜、案例類關系圖

在這個案例之中,類與類之間的關系如下:


  • Drink類就是前面說的抽象類(被裝飾者)
  • ShortBlack等就是單品咖啡
  • Decorator是一個裝飾者夫啊,含有一個被裝飾的對象(Drink drink)
  • Decorator的cost方法進行一個費用的疊加計算函卒,遞歸的計算價格

比如點:2份巧克力+1份牛奶的LongBlack,那么關系圖如下:



這圖表明了:

  • Milk包含了一個LongBlack
  • 1份Chocolate包含了(Milk+LongBlack)
  • 再來1份Chocolate包含了(Chocolate+Milk+LongBlack)

這樣的話不管是什么形式的單品咖啡+調料組合撇眯,都能通過遞歸方式方便的組合和維護报嵌。

2、案例代碼實現(xiàn)

(1)定義被裝飾者

先定義一個抽象的被裝飾者Drink類:

package com.cxc.decorator;

public abstract class Drink {

    public String des;  //描述

    private float price = 0.0f;

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    /**
     * 計算費用的抽象方法
     * @return
     */
    public abstract float cost();
}

coffee類:

package com.cxc.decorator;

public class Coffee extends Drink {

    @Override
    public float cost() {
        return super.getPrice();
    }
}

三個單品咖啡類:

package com.cxc.decorator;

public class Espresso extends Coffee {

    public Espresso() {
        setDes("意大利咖啡");
        setPrice(6.0f);
    }
}
package com.cxc.decorator;

public class LongBlack extends Coffee {

    public LongBlack() {
        setDes(" longblack ");
        setPrice(5.0f);
    }
}

package com.cxc.decorator;

public class ShortBlack extends Coffee {

    public ShortBlack() {

        setDes("shortblack");
        setPrice(4.0f);
    }
}

(2)定義裝飾者

定義裝飾者

package com.cxc.decorator;

public class Decorator extends Drink {
    private Drink drink;

    public Decorator(Drink drink) { //組合
        this.drink = drink;
    }

    @Override
    public float cost() {
        //getPrice() : 自己的價格
        //drink.cost() : 單品咖啡的價格
        return getPrice() + drink.cost();
    }

    @Override
    public String getDes() {
        //裝飾者描述+裝飾者價格+被裝飾者描述
        return des + " " + getPrice() + " && " + drink.getDes();
    }
}

定義具體的裝飾者子類(調料類):

package com.cxc.decorator;

/**
 * 具體的裝飾者叛本,在這里是調料
 */
public class Chocolate extends Decorator {

    public Chocolate(Drink drink) {
        super(drink);
        setDes(" 巧克力 ");
        setPrice(3.0f); //調味品價格
    }
}
package com.cxc.decorator;

public class Soy extends Decorator {
    public Soy(Drink drink) {
        super(drink);
        setDes("豆?jié){");
        setPrice(1.5f);
    }
}
package com.cxc.decorator;

public class Milk extends Decorator{
    public Milk(Drink drink) {
        super(drink);
        setDes("牛奶");
        setPrice(2.0f);
    }
}

主方法:

package com.cxc.decorator;

public class CofferBar {
    public static void main(String[] args) {

        //1.點一份LongBlack
        Drink order = new LongBlack();
        System.out.println("費用1="+order.cost());
        System.out.println("描述="+order.getDes());

        //2.加入一份牛奶
        order = new Milk(order);
        System.out.println("加入一份牛奶費用="+order.cost());
        System.out.println("加入一份牛奶描述="+order.getDes());

        //3.加一份巧克力
        order = new Chocolate(order);
        System.out.println("加入一份牛奶再加一份巧克力費用="+order.cost());
        System.out.println("加入一份牛奶再加一份巧克力描述="+order.getDes());

        //4.又加一份巧克力
        order = new Chocolate(order);
        System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力費用="+order.cost());
        System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力描述="+order.getDes());
    }
}

結果如下:


這樣改進后沪蓬,當我們需要再添加一個新的單品咖啡,只需要擴展一個新的單品咖啡類然后去繼承coffee即可来候;添加新的調料也是擴展新的調料類然后去繼承裝飾者即可跷叉。

六、JDK源碼使用到裝飾者模式的場景:IO流

Java的IO結構营搅,F(xiàn)ilterInputStream就是一個裝飾者:


  • InputStream是一個抽象類云挟,類似我們前面講的Drink。
  • FileInputStream等是InputStream子類转质,類似我們前面的LongBlack园欣。
  • FilterInputStream是InputStream子類,類似我們前面的Decorator裝飾者休蟹。
  • DataInputStream是FilterInputStream子類沸枯,具體的裝飾者,類似前面的Mile赂弓,Soy等绑榴。
  • FilterInputStream類有 InputStream成員變量,即含有被裝飾者盈魁。

由上面的分析翔怎,可以得出其用的就是裝飾者模式。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末杨耙,一起剝皮案震驚了整個濱河市赤套,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌珊膜,老刑警劉巖容握,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異车柠,居然都是意外死亡唯沮,警方通過查閱死者的電腦和手機脖旱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門堪遂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來介蛉,“玉大人,你說我怎么就攤上這事溶褪”揖桑” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵猿妈,是天一觀的道長吹菱。 經常有香客問我,道長彭则,這世上最難降的妖魔是什么鳍刷? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮俯抖,結果婚禮上输瓜,老公的妹妹穿的比我還像新娘。我一直安慰自己芬萍,他們只是感情好尤揣,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柬祠,像睡著了一般北戏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漫蛔,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天嗜愈,我揣著相機與錄音,去河邊找鬼莽龟。 笑死蠕嫁,一個胖子當著我的面吹牛,可吹牛的內容都是我干的轧房。 我是一名探鬼主播拌阴,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奶镶!你這毒婦竟也來了迟赃?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤厂镇,失蹤者是張志新(化名)和其女友劉穎纤壁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捺信,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡酌媒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年欠痴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秒咨。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡喇辽,死狀恐怖,靈堂內的尸體忽然破棺而出雨席,到底是詐尸還是另有隱情菩咨,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布陡厘,位于F島的核電站抽米,受9級特大地震影響,放射性物質發(fā)生泄漏糙置。R本人自食惡果不足惜云茸,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谤饭。 院中可真熱鬧标捺,春花似錦、人聲如沸网持。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽功舀。三九已至萍倡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辟汰,已是汗流浹背列敲。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帖汞,地道東北人戴而。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像翩蘸,于是被迫代替她去往敵國和親所意。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345