裝飾器模式
裝飾器模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有類的一個(gè)包裝。裝飾器在代碼程序中適用于以下場(chǎng)景:
- 用于擴(kuò)展 一個(gè)類的功能或者給一個(gè)類添加附加職責(zé)
- 動(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)。
- 裝飾者類擁有被裝飾者類的對(duì)象,一般是當(dāng)做構(gòu)造函數(shù)傳入的。
- 在裝飾者類當(dāng)中調(diào)用被裝飾者類的方法,封裝成新的功能方法。
- 裝飾者模式主要是利用多態(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)。