java設(shè)計(jì)模式-裝飾者模式詳解
引例
需求:設(shè)現(xiàn)在有單品咖啡:Espresso(意大利濃咖啡)和LongBlack(美式咖啡),調(diào)料有Milk(牛奶)和sugar(糖),客戶可以點(diǎn)單品咖啡或單品咖啡+調(diào)料的組合,計(jì)算相應(yīng)費(fèi)用。要求在擴(kuò)展新的咖啡種類時(shí),具有良好的擴(kuò)展性、改動(dòng)維護(hù)方便。
拋磚引玉,我們先看看兩種一般解法。
一般解法
方案一、
枚舉創(chuàng)建每一種組合可能,Drink抽象類表示飲料,cost()方法計(jì)算價(jià)格,子類如Longblack_Milk表示美式咖啡加牛奶:

這樣設(shè)計(jì)十分不明智,會(huì)有很多類,當(dāng)新增一個(gè)單品咖啡或調(diào)料時(shí),類的數(shù)量就會(huì)倍增,出現(xiàn)類爆炸。
方案二、
把調(diào)料內(nèi)置到Drink類,減少類數(shù)量過多:

方案二雖然不至于造成很多類,但是增加或刪除調(diào)料時(shí),代碼維護(hù)量仍舊很大。
裝飾者模式
裝飾者模式(Decorator Pattern)是結(jié)構(gòu)型模式,也稱裝飾器模式/修飾模式。它可以動(dòng)態(tài)的將新功能附加到對(duì)象上,同時(shí)又不改變其結(jié)構(gòu)。在對(duì)象功能擴(kuò)展方面,它比繼承更有彈性。這種模式創(chuàng)建了一個(gè)裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。
類圖:

- Component抽象類:主體,比如類似前面的Drink。
- ConcreteComponent類:具體的主體,比如前面的單品咖啡。
- Decorator類:裝飾者,比如前面的調(diào)料
- ConcreteDecorator類:具體的裝飾者,比如前面的牛奶。
如果ConcreteComponent具體子類很多,那么可以再加一個(gè)中間層,提取共同部分,通過繼承實(shí)現(xiàn)更多不同的具體子類。
裝飾者解法
類圖:

Drink 類就是前面說的抽象類Decorator 是一個(gè)裝飾類,含有一個(gè)被裝飾的對(duì)象(Drink obj)和的cost()方法進(jìn)行一個(gè)費(fèi)用的疊加計(jì)算,遞歸的計(jì)算價(jià)格Milk和Suger是具體的裝飾者Coffee是被裝飾者主體LongBlack和Espresso是具體實(shí)現(xiàn)的被裝飾者實(shí)體
代碼:
抽象類
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;
}
//計(jì)算費(fèi)用的抽象方法
public abstract float cost();
}
裝飾者
public class Decorator extends Drink {//裝飾者
private Drink obj;
public Decorator(Drink obj) { //組合
this.obj = obj;
}
@Override
public float cost() {
// getPrice 自己價(jià)格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 輸出被裝飾者的信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
public class Milk extends Decorator {//裝飾者子類
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
public class Suger extends Decorator {//裝飾者子類
public Suger(Drink obj) {
super(obj);
setDes(" 糖 ");
setPrice(1.0f);
}
}
被裝飾者
public class Coffee extends Drink {//被裝飾者
@Override
public float cost() {
return super.getPrice();
}
}
public class Espresso extends Coffee {//被裝飾者子類
public Espresso() {
setDes(" 意式咖啡 ");
setPrice(6.0f);
}
}
public class LongBlack extends Coffee {//被裝飾者子類
public LongBlack() {
setDes(" 美式咖啡 ");
setPrice(5.0f);
}
}
客戶端測(cè)試
public class Client {
public static void main(String[] args) {
// 阿姨的卡布奇諾:意式加兩份牛奶、一份糖
// 1. 點(diǎn)一份Espresso
Drink order = new Espresso();
System.out.println("order1 費(fèi)用=" + order.cost());
System.out.println("order1 描述=" + order.getDes());
// 2.1 order 加一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 費(fèi)用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 2.2 order 再加一份牛奶
order = new Milk(order);
System.out.println("order 加入兩份牛奶 費(fèi)用 =" + order.cost());
System.out.println("order 加入兩份牛奶 描述 = " + order.getDes());
// 3. order 加一份糖
order = new Suger(order);
System.out.println("order 兩份牛奶、一份糖 費(fèi)用 =" + order.cost());
System.out.println("order 兩份牛奶、一份糖 描述 = " + order.getDes());
System.out.println("===========================");
//美式咖啡加一份牛奶
//1. 點(diǎn)一份LongBlack
Drink order2 = new LongBlack();
System.out.println("order2 費(fèi)用 =" + order2.cost());
System.out.println("order2 描述 = " + order2.getDes());
//2. order2 加一份牛奶
order2 = new Milk(order2);
System.out.println("order2 加入一份牛奶 費(fèi)用 =" + order2.cost());
System.out.println("order2 加入一份牛奶 描述 = " + order2.getDes());
}
}
運(yùn)行結(jié)果:

總結(jié):
裝飾者模式就像打包一個(gè)快遞,不斷的動(dòng)態(tài)添加新的功能,可以組合出所有情況:

第一份Milk包含一份Espresso
第二份Milk包含(Milk+Espresso)
Suger包含(Milk+Milk+Espresso)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題最新解決方案
這篇文章主要介紹了本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題,本文給大家分享最新解決方案,感興趣的朋友跟隨小編一起看看吧2024-02-02
Spring Boot集成Resilience4J實(shí)現(xiàn)限流/重試/隔離
在Java的微服務(wù)生態(tài)中,對(duì)于服務(wù)保護(hù)組件,像springcloud的Hystrix,springcloud?alibaba的Sentinel,以及當(dāng)Hystrix停更之后官方推薦使用的Resilience4j,所以本文給大家介紹了Spring Boot集成Resilience4J實(shí)現(xiàn)限流/重試/隔離,需要的朋友可以參考下2024-03-03
基于mybatis-plus-generator實(shí)現(xiàn)代碼自動(dòng)生成器
這篇文章專門為小白準(zhǔn)備了入門級(jí)mybatis-plus-generator代碼自動(dòng)生成器,可以提高開發(fā)效率。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-05-05
SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
SpringBoot監(jiān)聽?wèi)?yīng)用程序啟動(dòng)的生命周期事件的四種方法
在 Spring Boot 中,監(jiān)聽?wèi)?yīng)用程序啟動(dòng)的生命周期事件有多種方法,本文給大家就介紹了四種監(jiān)聽?wèi)?yīng)用程序啟動(dòng)的生命周期事件的方法,并通過代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-07-07
SpringBoot 動(dòng)態(tài)定時(shí)器的使用方法
這篇文章主要介紹了SpringBoot 動(dòng)態(tài)定時(shí)器的使用方法,非常不錯(cuò),具有一定的參考借鑒借鑒價(jià)值,需要的朋友可以參考下2018-05-05
解析Java編程中設(shè)計(jì)模式的開閉原則的運(yùn)用
這篇文章主要介紹了解析Java編程中設(shè)計(jì)模式的開閉原則的運(yùn)用,開閉原則多應(yīng)用于Java程序的擴(kuò)展開發(fā)方面,需要的朋友可以參考下2016-02-02

