設(shè)計(jì)模式之裝飾者模式(Decorator Pattern)

裝飾者模式可以做到在不修改任何底層代碼的情況下,給對(duì)象增加的新的方法。
首先,我們通過(guò)對(duì)一個(gè)現(xiàn)實(shí)問(wèn)題的模擬分析,了解什么是裝飾者模式以及裝飾者模式的作用。


問(wèn)題提出

咖啡店在街頭隨處可見(jiàn)。我們以咖啡店的飲品訂單系統(tǒng)為例。假設(shè)我們要設(shè)計(jì)一個(gè)飲品的訂單系統(tǒng)。
設(shè)計(jì)了一個(gè)這樣的類(lèi)圖:

Paste_Image.png

Beverage是一個(gè) 抽象類(lèi),所有咖啡店的飲品都必須繼承這個(gè)類(lèi),description是飲品的描述信息,cost()是計(jì)算此種飲品的價(jià)格。

我們會(huì)遇到這樣的問(wèn)題,在購(gòu)買(mǎi)飲品的時(shí)候,我們可以要求在其中加入不同的調(diào)料配品,比如,摩卡(Mocha),加奶泡等。除了原本飲料需要的價(jià)格的外,咖啡店會(huì)根據(jù)所加入調(diào)料的再收取不同的費(fèi)用。

如果按照之前的設(shè)計(jì)方式,那么會(huì)出現(xiàn)如下的情況:

Paste_Image.png

** 顯然這似乎已經(jīng)是類(lèi)爆炸了!**

而且我們永遠(yuǎn)無(wú)法預(yù)測(cè),顧客會(huì)選取怎樣的調(diào)料的搭配,每當(dāng)出現(xiàn)一個(gè)新的調(diào)料搭配時(shí),我們就需要增加一個(gè)新的類(lèi)。
更加糟糕的是,當(dāng)原料配料的價(jià)格上漲后或者下降后,那么所有涉及到這種配料的類(lèi)都得重新改過(guò)。這簡(jiǎn)直是個(gè)噩夢(mèng)!很顯然這很不符合我們?cè)O(shè)計(jì)模式的原則。作為一個(gè)程序員,我們是決不能容忍這種情況發(fā)生的!

那么我們?cè)撊绾卧O(shè)計(jì)呢?

這里就需要用到我們的裝飾者模式!

引出裝飾者模式

讓我們轉(zhuǎn)換思路,我們以飲品beverage為主體,在運(yùn)行時(shí)以顧客選擇的調(diào)料來(lái)裝飾beverage。比如,如果顧客想要摩卡和奶泡的拿鐵咖啡,我們要做的應(yīng)該是這樣的:

  • 取一個(gè)拿鐵咖啡的對(duì)象
Paste_Image.png
  • 用摩卡對(duì)象裝飾它
Paste_Image.png
  • 用奶泡對(duì)象裝飾它
Paste_Image.png
  • 調(diào)用cost方法計(jì)算價(jià)錢(qián),并依賴(lài)委托將配料摩卡和奶泡加上去。
Paste_Image.png

會(huì)先計(jì)算whip的cost然后調(diào)用mocha的cost,然后調(diào)用拿鐵的cost,這樣就計(jì)算出了總價(jià)格。
這樣就是實(shí)現(xiàn)的裝飾者模式解決這個(gè)問(wèn)題的思路。
下面我們看一下裝飾者模式的定義,以及代碼實(shí)現(xiàn)的基本思路

定義裝飾者模式

裝飾者模式動(dòng)態(tài)的將責(zé)任附加到對(duì)象上。若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。

Paste_Image.png

這個(gè)類(lèi)圖就是裝飾者模式的實(shí)現(xiàn)方式。更詳細(xì)的是如下這個(gè)版本的類(lèi)圖。

Paste_Image.png

下面我們就根據(jù)這個(gè)類(lèi)圖來(lái)解決我們之前在實(shí)現(xiàn)咖啡店飲料系統(tǒng)上遇到的問(wèn)題。

Paste_Image.png

分析設(shè)計(jì)類(lèi)圖:

  • beverage相當(dāng)于抽象的component類(lèi),具體的component和decorator都需要繼承實(shí)現(xiàn)這個(gè)抽象類(lèi)。
  • 四個(gè)具體的飲料的類(lèi),相當(dāng)于concrete component!每一個(gè)類(lèi)代表了一個(gè)飲料類(lèi)型。
  • condimentDecorator是抽象的decorator類(lèi),它是所有調(diào)料類(lèi)的抽象,它保存了beverage的一個(gè)引用。
  • 調(diào)料裝飾者類(lèi)繼承自condimentDecorator,是各種具體調(diào)料的實(shí)現(xiàn),他們都實(shí)現(xiàn)了cost方法。

上面有一個(gè)非常關(guān)鍵的地方,就是我們注意到裝飾者和被裝飾者必須是一樣的類(lèi)型,也就是擁有共同的超類(lèi)。這樣做是因?yàn)槲覀円b飾者必須能取代被裝飾者。
這樣我們就可以利用對(duì)象的組合,將調(diào)料和飲料的行為組合起來(lái)。這符合我們之前提到的設(shè)計(jì)原則多用組合,少用繼承

實(shí)現(xiàn)裝飾者模式

如果看到這里還是不太清楚,也沒(méi)關(guān)系,接下來(lái)我們將具體實(shí)現(xiàn)代碼,對(duì)裝飾者模式有一個(gè)直觀根本的了解。

  • 首先實(shí)現(xiàn)beverage和condiment兩個(gè)抽象類(lèi)
package abstractComponent;

public abstract class Beverage {
    protected String description = "Unknow Beverage";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}
package abstractDecrator;
import abstractComponent.Beverage;


public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
  • 然后我們實(shí)現(xiàn)具體的飲料類(lèi)
package concreteComponent;
import abstractComponent.Beverage;


public class Coco extends Beverage {
    public Coco(){
        description = "Coco";
    }
    
    public double cost(){
        return 0.89;
    }
}
package concreteComponent;
import abstractComponent.Beverage;


public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    public double cost() {
        return 1.99;
    }
}
  • 我們?cè)賹?shí)現(xiàn)具體的裝飾者類(lèi),也就是調(diào)料類(lèi)
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Mocha extends CondimentDecorator {
    
    Beverage beverage;
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    
    
    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .20 + beverage.cost();
    }

    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Mocha";
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Soy extends CondimentDecorator {
    
    Beverage beverage;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .15 + beverage.cost();
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Whip extends CondimentDecorator {
    
    Beverage beverage;
    
    public Whip(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + " , whip";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .10 + beverage.cost();
    }

}
  • 最后編寫(xiě)一個(gè)測(cè)試類(lèi),來(lái)測(cè)試我們裝飾者模式的效果如何
import concreteComponent.Coco;
import concreteComponent.Espresso;
import concreteDecorator.Mocha;
import concreteDecorator.Soy;
import concreteDecorator.Whip;
import abstractComponent.Beverage;


public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Beverage beverage = new Espresso();
        System.out.println( beverage.getDescription() + "$" + beverage.cost());
        
        Beverage beverage2 = new Coco();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println( beverage2.getDescription() + "$" + beverage2.cost());
        
        Beverage beverage3 = new Espresso();
        beverage3 = new Whip(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println( beverage3.getDescription() + "$" + beverage3.cost());
    }

}

總結(jié)與分析

通過(guò)裝飾者模式我們可以很好的解決咖啡店的問(wèn)題,用裝飾者去包裝組件,可以達(dá)到很好的可擴(kuò)展性。

  • 裝飾者模式用到的技術(shù)主要有兩種就是組合和委托,這幫助我們動(dòng)態(tài)的在運(yùn)行時(shí)加上新的行為。
  • 裝飾者模式意味著一群裝飾者類(lèi),這些類(lèi)用來(lái)包裝裝飾者。
  • 裝飾者和被裝飾者類(lèi)實(shí)際上具有相同類(lèi)型的。
  • 裝飾者可以在被裝飾者的行為前面或后面加上自己的行為,甚至完全覆蓋。
  • 但裝飾者模式的使用會(huì)導(dǎo)致出現(xiàn)很多小對(duì)象,就是裝飾者對(duì)象,過(guò)度使用也會(huì)使程序變得復(fù)雜。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引言 在介紹裝飾者模式之前,我們先了解一個(gè)設(shè)計(jì)原則: 多用組合,少用繼承。 在平時(shí)寫(xiě)代碼時(shí),我們應(yīng)該減少類(lèi)繼承的使...
    Zentopia閱讀 4,376評(píng)論 4 11
  • 《Head First設(shè)計(jì)模式》讀書(shū)筆記 裝飾者模式 一,場(chǎng)景介紹 1,需求 一杯主飲料(Beverage)需要加...
    呆麻子閱讀 3,507評(píng)論 0 10
  • 設(shè)計(jì)模式之裝飾者模式 需求場(chǎng)景 咖啡店訂單系統(tǒng) 需求分析 咖啡種類(lèi)比較多,結(jié)賬的時(shí)候我們需要知道咖啡名和價(jià)格。首先...
    納薩立克閱讀 663評(píng)論 2 13
  • 距離國(guó)慶+中秋的8天小長(zhǎng)假還有2天就要來(lái)了, 炒雞雞凍有木有~ 想好這8天怎么過(guò)了嗎, 是出門(mén)浪還是準(zhǔn)備躺尸8天?...
    陳軒007閱讀 435評(píng)論 0 0
  • No.65 晚上濃重的霧,讓我看不清綿延的路。遠(yuǎn)處的霓虹燈閃著光,成了唯一的點(diǎn)綴,比星光離我更近,比時(shí)光離我更遠(yuǎn)。...
    水淺_bling閱讀 1,152評(píng)論 2 11

友情鏈接更多精彩內(nèi)容