裝飾者(裝飾器)模式

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有類的一個(gè)包裝。裝飾器在代碼程序中適用于以下場(chǎng)景:

  1. 用于擴(kuò)展 一個(gè)類的功能或者給一個(gè)類添加附加職責(zé)
  2. 動(dòng)態(tài)的給一個(gè)對(duì)象添加功能,這些功能可以再動(dòng)態(tài)的撤銷

為什么要用裝飾器模式

裝飾器模式能很好的解決過(guò)多的繼承所帶來(lái)的問(wèn)題,下面我們通過(guò)一個(gè)例子來(lái)看看裝飾器模式的作用。生活中我們很多人都很喜歡喝奶茶,只喝奶茶又感覺(jué)有點(diǎn)太單調(diào),會(huì)加一些配料,比如珍珠、脆啵啵、芋圓等,下面我們通過(guò)代碼來(lái)模擬一下。

最原始不放任何調(diào)料的特調(diào)奶茶

/**
 * @author: Winston
 * @createTime: 2021/7/1
 */
public class MilkTea {

    public String info() {
        return "特調(diào)奶茶";
    }

    public double price() {
        return 8D;
    }

}

由于我想吃點(diǎn)珍珠,于是我通過(guò)繼承的方式給奶茶增加珍珠

public class MilkTearWithPearl extends MilkTea {
    @Override
    public String info() {
        return super.info() + "加珍珠";
    }

    @Override
    public double price() {
        // 奶茶加上珍珠的價(jià)格
        return super.price() + 2D;
    }
}

我還想要一杯珍珠加上脆啵啵的奶茶

public class MilkTearWithPearlAndBoo extends MilkTea {
    @Override
    public String info() {
        return super.info() + "加珍珠加脆啵啵";
    }

    @Override
    public double price() {
        // 加珍珠加脆啵啵后的價(jià)格
        return super.price() + 2D + 3D;
    }
}

我同事想要一杯加珍珠加脆啵啵加西米露的奶茶

public class MilkTearWithPearlAndBooAndSago extends MilkTea {
    @Override
    public String info() {
        return super.info() + "加珍珠加脆啵啵加西米露";
    }

    @Override
    public double price() {
        // 加珍珠加脆啵啵加西米露后的價(jià)格
        return super.price() + 2D + 3D + 2D;
    }
}

測(cè)試類

public class MilkTeaTest {
    public static void main(String[] args) {
        MilkTea milkTea = new MilkTea();
        System.out.println(milkTea.info() + ",總價(jià)格:"+milkTea.price());
        System.out.println("====================================================");

        MilkTearWithPearl milkTearWithPearl = new MilkTearWithPearl();
        System.out.println(milkTearWithPearl.info() + ",總價(jià)格:"+milkTearWithPearl.price());
        System.out.println("====================================================");

        MilkTearWithPearlAndBoo milkTearWithPearlAndBoo = new MilkTearWithPearlAndBoo();
        System.out.println(milkTearWithPearlAndBoo.info() + ",總價(jià)格:"+milkTearWithPearlAndBoo.price());
        System.out.println("====================================================");

        MilkTearWithPearlAndBooAndSago milkTearWithPearlAndBooAndSago = new MilkTearWithPearlAndBooAndSago();
        System.out.println(milkTearWithPearlAndBooAndSago.info() + ",總價(jià)格:"+milkTearWithPearlAndBooAndSago.price());
        System.out.println("====================================================");

    }
}
特調(diào)奶茶,總價(jià)格:8.0
====================================================
特調(diào)奶茶加珍珠,總價(jià)格:10.0
====================================================
特調(diào)奶茶加珍珠加脆啵啵,總價(jià)格:13.0
====================================================
特調(diào)奶茶加珍珠加脆啵啵加西米露,總價(jià)格:15.0
====================================================

從上面的例子我們可以看出只要加的小料不同那么我就要新創(chuàng)建一個(gè)類來(lái)繼承奶茶類MikTea,如果只有三種料珍珠、芋圓、脆啵啵不重復(fù)添加,根據(jù)排列組合就有6種方式,更何況有可能有的人會(huì)要雙份珍珠,這么一來(lái)我們的類就特別特別多,非常的冗余。有沒(méi)有什么方式能夠進(jìn)行改造呢?下面我們就用裝飾器模式將上面的案例進(jìn)行改造,讓大家體會(huì)一下用了它有何不同。

裝飾器模式改造案例

首先先創(chuàng)造一個(gè)奶茶的抽象類MilkTea,所有的奶茶都必須有介紹信息和價(jià)格信息

public abstract class MilkTea {

    /**
     * 奶茶中的信息
     */
    protected abstract String info();

    /**
     * 奶茶總價(jià)格,這個(gè)方法需要在具體實(shí)現(xiàn)類中實(shí)現(xiàn)
     *
     * @return
     */
    protected abstract double price();

}

創(chuàng)造一個(gè)基礎(chǔ)款奶茶,要繼承基類MilkTea

public class BaseMilkTea extends MilkTea{

    private String info = "本店招牌特調(diào)奶茶";

    @Override
    protected String info() {
        return this.info;
    }

    @Override
    protected double price() {
        return 12D;
    }
}

再創(chuàng)建一個(gè)巧克力奶茶類,同樣要繼承基類MilkTea

public class ChocolateMilkTea extends MilkTea {

    private String info = "巧克力奶茶";
    
    @Override
    protected String info() {
        return this.info;
    }

    @Override
    protected double price() {
        return 15D;
    }
}

創(chuàng)建一個(gè)奶茶的裝飾類MilkTeaDecorate,同樣這個(gè)類也繼承MilkTea,這里大家可能會(huì)有疑問(wèn),既然MilkTeaDecorate這個(gè)抽象類的方法和MilkTea抽象類的方法一樣為什么還要單獨(dú)寫一個(gè)類。這里的話是為了能夠區(qū)分出哪個(gè)是裝飾者,哪個(gè)是被裝飾者。

public abstract class MilkTeaDecorate extends MilkTea {
    /**
     * 要添加小料的奶茶,通過(guò)構(gòu)造函數(shù)傳入奶茶信息
     */
    public MilkTea milkTea;

    public MilkTeaDecorate(MilkTea milkTea) {
        this.milkTea = milkTea;
    }


    /**
     * 所有的調(diào)料裝飾者都必須重新實(shí)現(xiàn)info()方法
     * 這樣才能夠得到所選奶茶的整體描述
     *
     * @return
     */
    @Override
    protected String info() {
        return this.info();
    }

    /**
     * 所有的調(diào)料裝飾者都必須重新實(shí)現(xiàn)price()方法
     * 這樣才能夠得到所選奶茶的整體價(jià)格
     *
     * @return
     */
    @Override
    protected double price() {
        return this.price();
    }
}

編寫珍珠裝飾類

public class PearlDecorate extends MilkTeaDecorate {


    public PearlDecorate(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    protected double price() {
        return this.milkTea.price() + 2D;
    }

    @Override
    protected String info() {
        return this.milkTea.info() + "加一份珍珠";
    }
}

脆啵啵裝飾類

public class BooDecorate extends MilkTeaDecorate {


    public BooDecorate(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    protected double price() {
        return this.milkTea.price() + 3D;
    }

    @Override
    protected String info() {
        return this.milkTea.info() + "加一份波波";
    }
}

西米露裝飾類

public class SagoDecorate extends MilkTeaDecorate {

    public SagoDecorate(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    protected double price() {
        return this.milkTea.price() + 3D;
    }

    @Override
    protected String info() {
        return this.milkTea.info() + "加一份西米露";
    }
}

測(cè)試類:

public class MilkTeaTest {
    public static void main(String[] args) {
        //1.一份原始的特調(diào)奶茶
        System.out.println("=======================================");
        MilkTea milkTea;
        milkTea = new BaseMilkTea();
        System.out.println(milkTea.info() + "總價(jià)價(jià)格:" + milkTea.price());
        // 想要加一份珍珠
        milkTea = new PearlDecorate(milkTea);
        System.out.println(milkTea.info() + "總價(jià)價(jià)格:" + milkTea.price());
        // 加一份脆啵啵
        milkTea = new BooDecorate(milkTea);
        System.out.println(milkTea.info() + "總價(jià)價(jià)格:" + milkTea.price());

        //1.一份巧克力奶茶
        System.out.println("=======================================");
        MilkTea milkTea2;
        milkTea2 = new ChocolateMilkTea();
        System.out.println(milkTea2.info() + "總價(jià)價(jià)格:" + milkTea2.price());
        // 想要加一份珍珠
        milkTea2 = new PearlDecorate(milkTea2);
        System.out.println(milkTea2.info() + "總價(jià)價(jià)格:" + milkTea2.price());
        // 加一份脆啵啵
        milkTea2 = new BooDecorate(milkTea2);
        System.out.println(milkTea2.info() + "總價(jià)價(jià)格:" + milkTea2.price());
        // 再加一份珍珠
        milkTea2 = new PearlDecorate(milkTea2);
        System.out.println(milkTea2.info() + "總價(jià)價(jià)格:" + milkTea2.price());
        System.out.println("=======================================");
    }
}

結(jié)果:

=======================================
本店招牌特調(diào)奶茶總價(jià)價(jià)格:12.0
本店招牌特調(diào)奶茶加一份珍珠總價(jià)價(jià)格:14.0
本店招牌特調(diào)奶茶加一份珍珠加一份波波總價(jià)價(jià)格:17.0
=======================================
巧克力奶茶總價(jià)價(jià)格:15.0
巧克力奶茶加一份珍珠總價(jià)價(jià)格:17.0
巧克力奶茶加一份珍珠加一份波波總價(jià)價(jià)格:20.0
巧克力奶茶加一份珍珠加一份波波加一份珍珠總價(jià)價(jià)格:22.0
=======================================

案例小結(jié)

通過(guò)上面這個(gè)案例,我們可以看出裝飾者(裝飾器)模式的特點(diǎn)。

  1. 裝飾者類擁有被裝飾者類的對(duì)象,一般是當(dāng)做構(gòu)造函數(shù)傳入的。
  2. 在裝飾者類當(dāng)中調(diào)用被裝飾者類的方法,封裝成新的功能方法。
  3. 裝飾者模式主要是利用多態(tài),將子類對(duì)象作為參數(shù)互相傳遞(主要是為了傳遞實(shí)現(xiàn)的函數(shù)),達(dá)到互相裝飾的效果,從而減少代碼重復(fù)率,優(yōu)化代碼結(jié)構(gòu)。

從上面的例子我們又可以看出跟靜態(tài)代理有所相似,靜態(tài)代理的被代理類和代理類都實(shí)現(xiàn)了同一接口,都是對(duì)類功能進(jìn)行加強(qiáng)。這邊裝飾者模式和靜態(tài)代理有何區(qū)別的?裝飾者模式和靜態(tài)代理的最大區(qū)別就是職責(zé)不同。代理模式(Proxy Pattern),為其它對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。裝飾模式(Decorator Pattern),動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。換句話說(shuō):代理模式的目標(biāo)是控制對(duì)被代理對(duì)象的訪問(wèn),而裝飾模式是給原對(duì)象增加額外功能。雖然代理模式也可以實(shí)現(xiàn)對(duì)被代理對(duì)象功能的增強(qiáng),但其核心是隱藏對(duì)被代理類的訪問(wèn)。

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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