詳解Java如何通過裝飾器模式擴(kuò)展系統(tǒng)功能
盡管目前房價(jià)依舊很高,買房的人依舊很多。如果大家買的是毛坯房,無疑還有一項(xiàng)艱巨的任務(wù)要面對(duì),那就是裝修。對(duì)新房進(jìn)行裝修并沒有改變房屋用于居住的本質(zhì),但它可以讓房子變得更漂亮、更溫馨、更實(shí)用、更能滿足居家的需求。在軟件設(shè)計(jì)中,我們也有一種類似新房裝修的技術(shù)可以對(duì)已有對(duì)象(新房)的功能進(jìn)行擴(kuò)展(裝修),以獲得更加符合用戶需求的對(duì)象,使得對(duì)象具有更加強(qiáng)大的功能。這種技術(shù)對(duì)應(yīng)于一種被稱之為裝飾模式的設(shè)計(jì)模式,本章將介紹用于擴(kuò)展系統(tǒng)功能的裝飾模式。
裝飾器模式概述
裝飾模式可以在不改變一個(gè)對(duì)象本身功能的基礎(chǔ)上給對(duì)象增加額外的新行為,在現(xiàn)實(shí)生活中,這種情況也到處存在,例如一張照片,我們可以不改變照片本身,給它增加一個(gè)相框,使得它具有防潮的功能,而且用戶可以根據(jù)需要給它增加不同類型的相框,甚至可以在一個(gè)小相框的外面再套一個(gè)大相框。
裝飾模式是一種用于替代繼承的技術(shù),它通過一種無須定義子類的方式來給對(duì)象動(dòng)態(tài)增加職責(zé),使用對(duì)象之間的關(guān)聯(lián)關(guān)系取代類之間的繼承關(guān)系。在裝飾模式中引入了裝飾類,在裝飾類中既可以調(diào)用待裝飾的原有類的方法,還可以增加新的方法,以擴(kuò)充原有類的功能。
裝飾模式結(jié)構(gòu)圖:
- Component(抽象構(gòu)件):它是具體構(gòu)件和抽象裝飾類的共同父類,聲明了在具體構(gòu)件中實(shí)現(xiàn)的業(yè)務(wù)方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對(duì)象以及裝飾之后的對(duì)象,實(shí)現(xiàn)客戶端的透明操作。
- ConcreteComponent(具體構(gòu)件):它是抽象構(gòu)件類的子類,用于定義具體的構(gòu)件對(duì)象,實(shí)現(xiàn)了在抽象構(gòu)件中聲明的方法,裝飾器可以給它增加額外的職責(zé)(方法)。
- Decorator(抽象裝飾類):它也是抽象構(gòu)件類的子類,用于給具體構(gòu)件增加職責(zé),但是具體職責(zé)在其子類中實(shí)現(xiàn)。它維護(hù)一個(gè)指向抽象構(gòu)件對(duì)象的引用,通過該引用可以調(diào)用裝飾之前構(gòu)件對(duì)象的方法,并通過其子類擴(kuò)展該方法,以達(dá)到裝飾的目的。
- ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負(fù)責(zé)向構(gòu)件添加新的職責(zé)。每一個(gè)具體裝飾類都定義了一些新的行為,它可以調(diào)用在抽象裝飾類中定義的方法,并可以增加新的方法用以擴(kuò)充對(duì)象的行為。
由于具體構(gòu)件類和裝飾類都實(shí)現(xiàn)了相同的抽象構(gòu)件接口,因此裝飾模式以對(duì)客戶透明的方式動(dòng)態(tài)地給一個(gè)對(duì)象附加上更多的責(zé)任,換言之,客戶端并不會(huì)覺得對(duì)象在裝飾前和裝飾后有什么不同。裝飾模式可以在不需要?jiǎng)?chuàng)造更多子類的情況下,將對(duì)象的功能加以擴(kuò)展。
簡單案例
場(chǎng)景:天氣太熱了,喝點(diǎn)兒冰水解解暑;加點(diǎn)兒檸檬片,讓果汁好喝點(diǎn)兒。
先定義一個(gè)喝水的接口
public interface Drink { /** * 喝水 */ void drink(); }
寫一個(gè)接口的實(shí)現(xiàn)
public class DrinkWater implements Drink { @Override public void drink() { System.out.println("喝水"); } }
一個(gè)簡單的裝飾器
public class DrinkDecorator implements Drink { private final Drink drink; public DrinkDecorator(Drink drink) { this.drink = drink; } @Override public void drink() { System.out.println("先加點(diǎn)兒檸檬片"); drink.drink(); } }
測(cè)試
public class DrinkMain { public static void main(String[] args) { Drink drink = new DrinkWater(); drink = new DrinkDecorator(drink); drink.drink(); } }
運(yùn)行結(jié)果
先加點(diǎn)兒檸檬片
喝水
一個(gè)簡單的裝飾器模式例子就寫完了,這里只是先了解一下裝飾器模式。
案例場(chǎng)景
大家應(yīng)該都喝過奶茶吧,我經(jīng)常喝的原味奶茶,里面可以添加配料,每增加一種配料,該奶茶的價(jià)格就會(huì)增加,要求計(jì)算一種奶茶的價(jià)格。
下面就可以使用裝飾器模式來模擬這種場(chǎng)景。
定義抽象構(gòu)件類
/** * 奶茶 */ public interface MilkyTea { double cost(); }
定義具體構(gòu)件類
/** * 珍珠奶茶 */ public class PearlMilkyTea implements MilkyTea{ @Override public double cost() { return 13.0; } }
定義抽象裝飾類
public abstract class MilkyTeaDecorator implements MilkyTea{ private MilkyTea milkyTea; public MilkyTeaDecorator(MilkyTea milkyTea){ this.milkyTea = milkyTea; } @Override public double cost() { return milkyTea.cost(); } }
定義具體裝飾類
/** * 椰果奶茶 */ public class CoconutDecorator extends MilkyTeaDecorator{ public CoconutDecorator(MilkyTea milkyTea) { super(milkyTea); } @Override public double cost() { System.out.println("添加椰果"); return super.cost() + 2.0; } } /** * 布丁奶茶 */ public class PuddingDecorator extends MilkyTeaDecorator{ public PuddingDecorator(MilkyTea milkyTea) { super(milkyTea); } @Override public double cost() { System.out.println("添加布丁"); return super.cost() + 1.5; } }
測(cè)試代碼
public class Client { public static void main(String[] args) { MilkyTea milkyTea = new PearlMilkyTea(); milkyTea = new CoconutDecorator(milkyTea); milkyTea = new PuddingDecorator(milkyTea); System.out.println(milkyTea.cost()); } }
運(yùn)行結(jié)果
添加布丁
添加椰果
16.5
在客戶端代碼中,我們先定義了一個(gè)MilkyTea類型的具體構(gòu)件對(duì)象milkyTea
,然后將milkyTea
作為構(gòu)造函數(shù)的參數(shù)注入到具體裝飾類CoconutDecorator中,得到一個(gè)裝飾之后對(duì)象milkyTea
,再將裝飾了一次之后的milkyTea
對(duì)象注入另一個(gè)裝飾類PuddingDecorator中實(shí)現(xiàn)第二次裝飾,得到一個(gè)經(jīng)過兩次裝飾的對(duì)象milkyTea,再調(diào)用milkyTea的cost()方法即可得到一杯既加了布丁又加了椰果的珍珠奶茶價(jià)格。
裝飾器模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 裝飾類和被裝飾類可以獨(dú)立發(fā)展,不會(huì)相互耦合。
- 相比于繼承,更加的輕便、靈活。
- 可以動(dòng)態(tài)擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能,不必修改原本代碼
缺點(diǎn)
- 會(huì)產(chǎn)生很多的裝飾類,增加了系統(tǒng)的復(fù)雜性。
- 這種比繼承更加靈活機(jī)動(dòng)的特性,也同時(shí)意味著裝飾模式比繼承易于出錯(cuò),排錯(cuò)也很困難,對(duì)于多次裝飾的對(duì)象,調(diào)試時(shí)尋找錯(cuò)誤可能需要逐級(jí)排查,較為繁瑣。
裝飾器模式適用場(chǎng)景
在不影響其他對(duì)象的情況下,以動(dòng)態(tài)、透明的方式給單個(gè)對(duì)象添加職責(zé)。
當(dāng)不能采用繼承的方式對(duì)系統(tǒng)進(jìn)行擴(kuò)展或者采用繼承不利于系統(tǒng)擴(kuò)展和維護(hù)時(shí)可以使用裝飾模式。不能采用繼承的情況主要有兩類:第一類是系統(tǒng)中存在大量獨(dú)立的擴(kuò)展,為支持每一種擴(kuò)展或者擴(kuò)展之間的組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長;第二類是因?yàn)轭愐讯x為不能被繼承(如Java語言中的final類)。
總結(jié)
裝飾模式降低了系統(tǒng)的耦合度,可以動(dòng)態(tài)增加或刪除對(duì)象的職責(zé),并使得需要裝飾的具體構(gòu)件類和具體裝飾類可以獨(dú)立變化,以便增加新的具體構(gòu)件類和具體裝飾類。在軟件開發(fā)中,裝飾模式應(yīng)用較為廣泛,例如在JavaIO中的輸入流和輸出流的設(shè)計(jì)、javax.swing包中一些圖形界面構(gòu)件功能的增強(qiáng)等地方都運(yùn)用了裝飾模式。
到此這篇關(guān)于詳解Java如何通過裝飾器模式擴(kuò)展系統(tǒng)功能的文章就介紹到這了,更多相關(guān)Java裝飾器模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)加載權(quán)限信息的方法
這篇文章主要介紹了SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)加載權(quán)限信息,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定需要的朋友可以參考下2022-01-01使用dom4j解析xml文件,并轉(zhuǎn)出json格式問題
這篇文章主要介紹了使用dom4j解析xml文件,并轉(zhuǎn)出json格式問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo)的操作代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09mybatis報(bào)錯(cuò)元素內(nèi)容必須由格式正確的字符數(shù)據(jù)或標(biāo)記組成異常的解決辦法
今天小編就為大家分享一篇關(guān)于mybatis查詢出錯(cuò)解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Java中的位運(yùn)算符號(hào)解讀(&、|、^、~、<<、>>、>>>)
這篇文章主要介紹了Java中的位運(yùn)算符號(hào)(&、|、^、~、<<、>>、>>>),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Spring引入外部屬性文件配置數(shù)據(jù)庫連接的步驟詳解
這篇文章主要介紹了Spring引入外部屬性文件配置數(shù)據(jù)庫連接的步驟詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Java設(shè)計(jì)模式之觀察者模式原理與用法詳解
這篇文章主要介紹了Java設(shè)計(jì)模式之觀察者模式,結(jié)合實(shí)例形式詳細(xì)分析了Java設(shè)計(jì)模式之觀察者模式基本概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-06-06