Java裝飾模式

1 概述

在一個(gè)項(xiàng)目中,你會(huì)有非常多的因素考慮不到,特別是業(yè)務(wù)的變更,不時(shí)的冒出一個(gè)需求是很正常的情況。有三個(gè)繼承關(guān)系的類(lèi):Father、Son、GrandSon,我們要在Son類(lèi)上增強(qiáng)一些功能怎么辦?給Son類(lèi)增加方法嗎?那對(duì)GrandSon的影響呢?特別是對(duì)GrandSon有多個(gè)的情況,你會(huì)怎么辦?認(rèn)真看完本文,你會(huì)找到你的答案。JavaIO中,像下面的嵌套語(yǔ)句是不是很常見(jiàn),為什么要怎樣定義呢?理解裝飾模式后,你會(huì)找到答案。DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“FileTest.Java”)));

2《設(shè)計(jì)模式之禪》中的例子

成績(jī)單需要父母簽名這事很多人都經(jīng)歷過(guò),這舉這樣一個(gè)例子:


代碼清單1 抽象成績(jī)單

//抽象成績(jī)單  
public abstract class SchoolReport {  
    //展示成績(jī)情況  
    public abstract void report();  
    //家長(zhǎng)簽字  
    public abstract void sign(String name);  
}

代碼清單2 四年級(jí)成績(jī)單

//四年級(jí)成績(jī)單  
public class FouthGradeSchoolReport extends SchoolReport{  
    //我的成績(jī)單  
    public void report(){  
        //成績(jī)單的格式是這個(gè)樣子的  
        System.out.println("尊敬的XXX家長(zhǎng):");  
        System.out.println("······");  
        System.out.println("語(yǔ)文62 數(shù)學(xué)65 體育98 自然63");  
        System.out.println("······");  
        System.out.println("      家長(zhǎng)簽字");  
    }  
    //家長(zhǎng)簽名  
    public void sign(String name){  
        System.out.println("家長(zhǎng)簽字為:" + name);  
    }  
}

代碼清單3 老爸查看成績(jī)單

//老爸查看成績(jī)單  
public class Father {  
    public static void main(String[] args) {  
        //把成績(jī)單拿過(guò)來(lái)  
        SchoolReport sr = new FouthGradeSchoolReport();  
        //看成績(jī)單  
        sr.report();  
        //簽名? 休想!  
    }  
}  
/*Output: 
尊敬的XXX家長(zhǎng): 
······ 
語(yǔ)文62 數(shù)學(xué)65 體育98 自然63 
······ 
      家長(zhǎng)簽字 
 */  

就這成績(jī)還要我簽字?!老爸就開(kāi)始找掃帚,我開(kāi)始做準(zhǔn)備:深呼吸,繃緊肌肉,提臀,收腹。 哈哈,幸運(yùn)的是,這個(gè)不是當(dāng)時(shí)的真實(shí)情況,我沒(méi)有直接把成績(jī)單交給老爸,而是在交給他之前做了點(diǎn)技術(shù)工作,我要把成績(jī)單封裝一下,封裝分類(lèi)兩步來(lái)實(shí)現(xiàn), 如下所示。

  • 匯報(bào)最高成績(jī)
    跟老爸說(shuō)各個(gè)科目的最高分,語(yǔ)文最高是75,數(shù)學(xué)是78,自然是80,然后老爸覺(jué)得我的成績(jī)與最高分?jǐn)?shù)相差不多,考的還是不錯(cuò)的嘛!這個(gè)是實(shí)情,但是不知道是什么原因,反正期末考試都考得不怎么樣,但是基本上都集中在70分以上,我這60多分基本上還是墊底的角色。
  • 匯報(bào)排名情況
    在老爸看完成績(jī)單后,告訴他我在全班排第38名,這個(gè)也是實(shí)情,為啥呢?有將近十個(gè)同學(xué)退學(xué)了! 這個(gè)情況我是不會(huì)說(shuō)的。 不知道是不是當(dāng)時(shí)第一次發(fā)成績(jī)單時(shí)學(xué)校沒(méi)有考慮清楚,沒(méi)有寫(xiě)上總共有多少同學(xué),排第幾名,反正是被我鉆了個(gè)空子。
    那修飾是說(shuō)完了,我們看看類(lèi)圖如何修改,如下圖所示:


代碼清單4 修飾成績(jī)單

//修飾成績(jī)單  
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {  
    // 首先要定義你要美化的方法, 先給老爸說(shuō)學(xué)校最高成績(jī)  
    private void reportHighScore() {  
        System.out.println("這次考試語(yǔ)文最高是75, 數(shù)學(xué)是78, 自然是80");  
    }
    // 在老爸看完畢成績(jī)單后,我再匯報(bào)學(xué)校的排名情況  
    private void reportSort() {  
        System.out.println("我是排名第38名...");  
    }
    // 由于匯報(bào)的內(nèi)容已經(jīng)發(fā)生變更,那所以要重寫(xiě)父類(lèi)  
    @Override  
    public void report() {  
        this.reportHighScore(); // 先說(shuō)最高成績(jī)  
        super.report(); // 然后老爸看成績(jī)單  
        this.reportSort(); // 然后告訴老爸學(xué)習(xí)學(xué)校排名  
    }  
}  

代碼清單5 老爸查看修飾后的成績(jī)單

public class Father2 {  
    public static void main(String[] args) {  
        // 把美化過(guò)的成績(jī)單拿過(guò)來(lái)  
        SchoolReport sr = new SugarFouthGradeSchoolReport();  
        // 看成績(jī)單  
        sr.report();  
        // 然后老爸, 一看, 很開(kāi)心, 就簽名了  
        sr.sign("老三"); // 我叫小三, 老爸當(dāng)然叫老三  
    }  
}  
/* 
這次考試語(yǔ)文最高是75, 數(shù)學(xué)是78, 自然是80 
尊敬的XXX家長(zhǎng): 
······ 
語(yǔ)文62 數(shù)學(xué)65 體育98 自然63 
······ 
      家長(zhǎng)簽字 
我是排名第38名... 
家長(zhǎng)簽字為:老三 
 * */ 

通過(guò)繼承確實(shí)能夠解決這個(gè)問(wèn)題,老爸看成績(jī)單很開(kāi)心,然后就給簽字了,但現(xiàn)實(shí)的情況是很復(fù)雜的,可能老爸聽(tīng)我匯報(bào)最高成績(jī)后,就直接樂(lè)開(kāi)花了,直接簽名了,后面的排名就沒(méi)必要看了,或者老爸要先看排名情況,那怎么辦? 繼續(xù)擴(kuò)展?你能擴(kuò)展多少個(gè)類(lèi)?這還是一個(gè)比較簡(jiǎn)單的場(chǎng)景,一旦需要裝飾的條件非常多,比如20個(gè),你還通過(guò)繼承來(lái)解決,你想象的子類(lèi)有多少個(gè)? 你是不是馬上就要崩潰了!
好,你也看到通過(guò)繼承情況確實(shí)出現(xiàn)了問(wèn)題,類(lèi)爆炸,類(lèi)的數(shù)量激增,光寫(xiě)這些類(lèi)不累死你才怪,而且還要想想以后維護(hù)怎么辦,誰(shuí)愿意接收這么一大攤本質(zhì)相似的代碼維護(hù)工作?并且在面向?qū)ο蟮脑O(shè)計(jì)中,如果超過(guò)兩層繼承,你就應(yīng)該想想是不是出設(shè)計(jì)問(wèn)題了,是不是應(yīng)該重新找一條康莊大道了,這是經(jīng)驗(yàn)值,不是什么絕對(duì)的,繼承層次越多以后的維護(hù)成本越多,問(wèn)題這么多,那怎么辦?好辦,我們定義一批專(zhuān)門(mén)負(fù)責(zé)裝飾的類(lèi),然后根據(jù)實(shí)際情況來(lái)決定是否需要進(jìn)行裝飾,類(lèi)圖稍做修正,如圖17-4所示。



增加一個(gè)抽象類(lèi)和兩個(gè)實(shí)現(xiàn)類(lèi),其中Decorator的作用是封裝SchoolReport類(lèi),如果大家還記得代理模式,那么很容易看懂這個(gè)類(lèi)圖,裝飾類(lèi)的作用也就是一個(gè)特殊的代理類(lèi),真實(shí)的執(zhí)行者還是被代理的角色FouthGradeSchoolReport。

/**
 * 抽象類(lèi),也是最核心的頂層對(duì)象
 */
public abstract class SchoolReport {
    // 報(bào)告成績(jī)
    public abstract void report();
    // 家長(zhǎng)簽字
    public abstract void sign(String name);
}
/**
 * 最核心、最原始、最基本的接口或抽象類(lèi)的實(shí)現(xiàn),你要裝飾的就是它
 */
public class ConcreateSchoolReport extends SchoolReport {
    @Override
    public void report() {
        System.out.println("報(bào)告家長(zhǎng),您的孩子成績(jī)不及格");
    }
    @Override
    public void sign(String name) {
        System.out.println("家長(zhǎng)簽名:" + name);
    }
}
/**
 * 裝飾的抽象類(lèi),這是一個(gè)特殊的代理類(lèi)(代理模式),具體執(zhí)行的方法還是由傳入進(jìn)來(lái)的SchoolReport(具體實(shí)現(xiàn)類(lèi))決定
 */
public abstract class Decorator extends SchoolReport {
    private SchoolReport schoolReport;
    // 構(gòu)造函數(shù),傳遞SchoolReport對(duì)象并保存在私有屬性中
    public Decorator(SchoolReport schoolReport){
        this.schoolReport = schoolReport;
    }
    // 具體執(zhí)行的report方法由傳進(jìn)來(lái)的SchoolReport決定
    public void report() {
        this.schoolReport.report();
    }
    public void sign(String name) {
        this.schoolReport.sign(name);
    }
}
/**
 * 具體的裝飾類(lèi),就是由他進(jìn)行對(duì)象的裝飾
 */
public class HighScoreDecorator extends Decorator {
    // 由于父類(lèi)顯示的定義了構(gòu)造方法,這里也必須顯示定義
    public HighScoreDecorator(SchoolReport schoolReport) {
        super(schoolReport);
    }
    // 自定義的修飾方法,此方法用于修飾具體的SchoolReport對(duì)象ConcreateSchoolReport
    public void highCore(){
        System.out.println("其實(shí)大家都不及格,你兒子分算高的");
    }
    //重寫(xiě)report方法,將自定義方法放在父類(lèi)的report方法執(zhí)行之前執(zhí)行,用于修飾
    @Override
    public void report() {
        highCore();
        super.report();
    }
}
public class OrderSchoolReport extends Decorator {
    // 由于父類(lèi)顯示的定義了構(gòu)造方法,這里也必須顯示定義
    public OrderSchoolReport(SchoolReport schoolReport) {
        super(schoolReport);
    }
    // 自定義的修飾方法,此方法用于修飾具體的SchoolReport對(duì)象ConcreateSchoolReport
    public void order(){
        System.out.println("而且你兒子的分?jǐn)?shù)排名全班第一");
    }
    //重寫(xiě)report方法,將自定義方法放在父類(lèi)的report方法執(zhí)行之前執(zhí)行,用于修飾
    @Override
    public void report() {
        order();
        super.report();
    }
}
/**
 * 客戶(hù)端調(diào)用
 */
public class Client {
    public static void main(String[] args) {
        SchoolReport report = new ConcreateSchoolReport();
        report = new HighScoreDecorator(report);
        report = new OrderSchoolReport(report);
        report.report();
        report.sign("NB");
    }
}

3 裝飾模式的定義

裝飾模式(Decorator Pattern)是一種比較常見(jiàn)的模式,其定義如下:Attach additionalresponsibilities to an object dynamically keeping the same interface.Decoratorsprovide a flexible alternative to subclassing for extending functionality.( 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō),裝飾模式相比生成子類(lèi)更為靈活。)
裝飾模式的通用類(lèi)圖如圖5所示。


4

在類(lèi)圖中,有四個(gè)角色需要說(shuō)明:

  • Component抽象構(gòu)件
    Component是一個(gè)接口或者是抽象類(lèi),就是定義我們最核心的對(duì)象,也就是最原始的對(duì)象,如上面的成績(jī)單。
    注意 在裝飾模式中,必然有一個(gè)最基本、最核心、最原始的接口或抽象類(lèi)充當(dāng)Component抽象構(gòu)件。
  • ConcreteComponent 具體構(gòu)件
    ConcreteComponent是最核心、最原始、最基本的接口或抽象類(lèi)的實(shí)現(xiàn),你要裝飾的就是它。
  • Decorator裝飾角色
    一般是一個(gè)抽象類(lèi),做什么用呢?實(shí)現(xiàn)接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的屬性里必然有一個(gè)private變量指向Component抽象構(gòu)件。
  • 具體裝飾角色
    ConcreteDecoratorA和ConcreteDecoratorB是兩個(gè)具體的裝飾類(lèi),你要把你最核心的、最原始的、最基本的東西裝飾成其他東西,上面的例子就是把一個(gè)比較平庸的成績(jī)單裝飾成家長(zhǎng)認(rèn)可的成績(jī)單。

理解client中的new

public class Client {
    public static void main(String[] args) {
        //這里創(chuàng)建了一個(gè)ConcreateComponent對(duì)象,返回對(duì)象的引用component
        Component component = new ConcreateComponent();
        //這里將上述對(duì)象傳入ConcreateDecoratorA中,通過(guò)其構(gòu)造方法將component傳入到Decorato中
        //對(duì)象:一個(gè)Decorato對(duì)象,其中私有屬性component保存為ConcreateComponent
        //     一個(gè)ConcreateDecoratorA對(duì)象component
        component = new ConcreateDecoratorA(component);
        //這里將ConcreateDecoratorA傳入ConcreateDecoratorB中,通過(guò)其構(gòu)造方法將其傳入到Decorato中
        //對(duì)象:一個(gè)Decorato對(duì)象,其中私有屬性component保存為ConcreateDecoratorA
        //     一個(gè)ConcreateDecoratorB對(duì)象component
        component = new ConcreateDecoratorB(component);
        //先執(zhí)行ConcreateDecoratorB中的operate方法,再執(zhí)行其父類(lèi)Decorato中的operate,注意此時(shí)的Decorato方法operate會(huì)調(diào)用ConcreateDecoratorA中的operate,
        //該方法又會(huì)調(diào)用ConcreateComponent中的operate方法,故只需要在ConcreateDecoratorA、B兩個(gè)修飾類(lèi)的operate中加上具體修飾的方法即可完成鏈?zhǔn)降恼{(diào)用
        component.operate();
    }
}

4 半透明的裝飾模式

4.1 裝飾模式的簡(jiǎn)化

如果只有一個(gè)ConcreteComponent類(lèi),那么可以考慮去掉抽象的Component類(lèi)(接口),把Decorator作為一個(gè)ConcreteComponent子類(lèi)。如下圖所示:


5

如果只有一個(gè)ConcreteDecorator類(lèi),那么就沒(méi)有必要建立一個(gè)單獨(dú)的Decorator類(lèi),而可以把Decorator和ConcreteDecorator的責(zé)任合并成一個(gè)類(lèi)。甚至在只有兩個(gè)ConcreteDecorator類(lèi)的情況下,都可以這樣做。如下圖所示


6

4.2 透明性的要求

裝飾模式對(duì)客戶(hù)端的透明性要求程序不要聲明一個(gè)ConcreteComponent類(lèi)型的變量,而應(yīng)當(dāng)聲明一個(gè)Component類(lèi)型的變量。用上面成績(jī)單的例子來(lái)說(shuō)有:SchoolReport sr;sr = new FouthGradeSchoolReport();sr = new HighScoreDecorator(sr);sr = new SortDecorator(sr);而下面的做法是不對(duì)的:HighScoreDecorator hd = new HighScoreDecorator(sr);SortDecorator sd = new SortDecorator(sr);

4.3 半透明的裝飾模式

然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強(qiáng)所考慮的類(lèi)的性能。在增強(qiáng)性能的時(shí)候,往往需要建立新的公開(kāi)的方法。上面成績(jī)單的例子中,顯示前十名學(xué)生信息。這就意味著SortDecorator類(lèi)中應(yīng)當(dāng)有一個(gè)新的displayTopTen()方法。再比如,顯示顯示各科最高分學(xué)生信息,這就意味著在HighScoreDecorator類(lèi)里應(yīng)當(dāng)有一個(gè)新的showTop()方法。這就導(dǎo)致了大多數(shù)的裝飾模式的實(shí)現(xiàn)都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。這意味著客戶(hù)端可以聲明ConcreteDecorator類(lèi)型的變量,從而可以調(diào)用ConcreteDecorator類(lèi)中才有的方法:SchoolReport sr = new SortDecorator();SortDecorator sd = new SortDecorator();sd.displayTopTen();半透明的裝飾模式是介于裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類(lèi)的接口,也可以通過(guò)改寫(xiě)一個(gè)或幾個(gè)方法,或增加新的方法來(lái)增強(qiáng)或改變所考慮的類(lèi)的功能。大多數(shù)的裝飾模式實(shí)際上是半透明的裝飾模式,這樣的裝飾模式也稱(chēng)做半裝飾、半適配器模式。裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過(guò)封裝其他對(duì)象達(dá)到設(shè)計(jì)的目的的,但是它們的形態(tài)有很大區(qū)別。理想的裝飾模式在對(duì)被裝飾對(duì)象進(jìn)行功能增強(qiáng)的同時(shí),要求具體構(gòu)件角色、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致。而適配器模式則不然,一般而言,適配器模式并不要求對(duì)源對(duì)象的功能進(jìn)行增強(qiáng),但是會(huì)改變?cè)磳?duì)象的接口,以便和目標(biāo)接口相符合。裝飾模式有透明和半透明兩種,這兩種的區(qū)別就在于裝飾角色的接口與抽象構(gòu)件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式,要求具體構(gòu)件角色、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致。相反,如果裝飾角色的接口與抽象構(gòu)件角色接口不一致,也就是說(shuō)裝飾角色的接口比抽象構(gòu)件角色的接口寬的話(huà),裝飾角色實(shí)際上已經(jīng)成了一個(gè)適配器角色,這種裝飾模式也是可以接受的,稱(chēng)為“半透明”的裝飾模式,如下圖所示。


7

在適配器模式里面,適配器類(lèi)的接口通常會(huì)與目標(biāo)類(lèi)的接口重疊,但往往并不完全相同。換言之,適配器類(lèi)的接口會(huì)比被裝飾的目標(biāo)類(lèi)接口寬。顯然,半透明的裝飾模式實(shí)際上就是處于適配器模式與裝飾模式之間的灰色地帶。如果將裝飾模式與適配器模式合并成為一個(gè)“包裝模式”的話(huà),那么半透明的裝飾模式倒可以成為這種合并后的“包裝模式”的代表。

5 裝飾模式應(yīng)用

5.1 裝飾模式的優(yōu)點(diǎn)

裝飾類(lèi)和被裝飾類(lèi)可以獨(dú)立發(fā)展,而不會(huì)相互耦合。換句話(huà)說(shuō),Component類(lèi)無(wú)須知道Decorator類(lèi),Decorator類(lèi)是從外部來(lái)擴(kuò)展Component類(lèi)的功能,而Decorator也不用知道具體的構(gòu)件。
裝飾模式是繼承關(guān)系的一個(gè)替代方案。我們看裝飾類(lèi)Decorator,不管裝飾多少層,返回的對(duì)象還是Component,實(shí)現(xiàn)的還是is-a的關(guān)系。
裝飾模式與繼承關(guān)系的目的都是要擴(kuò)展對(duì)象的功能,但是裝飾模式可以提供比繼承更多的靈活性。裝飾模式允許系統(tǒng)動(dòng)態(tài)決定“貼上”一個(gè)需要的“裝飾”,或者除掉一個(gè)不需要的“裝飾”。繼承關(guān)系則不同,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了。
過(guò)使用不同的具體裝飾類(lèi)以及這些裝飾類(lèi)的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同行為的組合。

5.2 裝飾模式的缺點(diǎn)

由于使用裝飾模式,可以比使用繼承關(guān)系需要較少數(shù)目的類(lèi)。使用較少的類(lèi),當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是,在另一方面,使用裝飾模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對(duì)象。更多的對(duì)象會(huì)使得查錯(cuò)變得困難,特別是這些對(duì)象看上去都很相像。
多層的裝飾是比較復(fù)雜的。

5.3 裝飾模式的使用場(chǎng)景

需要擴(kuò)展一個(gè)類(lèi)的功能,或給一個(gè)類(lèi)增加附加功能。
需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)地撤銷(xiāo)。
需要為一批的兄弟類(lèi)進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式。
裝飾模式在Java語(yǔ)言中的最著名的應(yīng)用:Java I/O標(biāo)準(zhǔn)庫(kù)的設(shè)計(jì)了

最后編輯于
?著作權(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)容

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