23種設(shè)計(jì)模式-裝飾模式

  1. 罪惡的成績(jī)單

考試成績(jī)單以及成績(jī)排名,大家都懂得,以前上學(xué)的時(shí)候,這玩意往家里寄是真的要命。成績(jī)單還需要家長(zhǎng)簽字,那么,我們來(lái)模擬將成績(jī)單給家長(zhǎng)簽字這一情景,如圖17-1:


17-1

我們先來(lái)看成績(jī)單的抽象類SchoolReport,代碼如下:

public abstract class SchoolReport {
    //顯示成績(jī)情況
    public abstract void report();
    //要家長(zhǎng)簽字
    public abstract void sign();
}

有抽象類了,在來(lái)看看具體的四年級(jí)成績(jī)單FouthGradeSchoolReport,代碼如下:

public class FouthGradeSchoolReport extends SchoolReport {
    @Override
    public void report() {
        //成績(jī)單的格式
        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)簽名:");
    }

    @Override
    public void sign(String name) {
        System.out.println("家長(zhǎng)簽名:" + name);
    }
}

成績(jī)單出來(lái)62、65之類的在小學(xué)基本就是墊底,那么我們把成績(jī)單帶回去給家長(zhǎng)看,修改一下類圖,如圖17-2:


17-2

這是原裝的成績(jī)單,沒有動(dòng)過(guò)的,來(lái)看Father類代碼如下:

public class Father {
    public static void main(String[] args) {
        SchoolReport sc = new FouthGradeSchoolReport();
        //看成績(jī)單
        sc.report();
        //簽名?休想,一頓胖揍
    }
}

這個(gè)成績(jī)直接拿出來(lái)肯定是找打,我們把成績(jī)單封裝一下,封裝分為兩步來(lái)實(shí)現(xiàn):

  • 匯報(bào)最高成績(jī)
    跟家長(zhǎng)說(shuō)各個(gè)科目的最高分,語(yǔ)文最高75,數(shù)學(xué)最高78,自然是80;那么家長(zhǎng)一看你的成績(jī)和最高分差不多,也還行;實(shí)際普遍在70以上,這個(gè)60多還是墊底
  • 匯報(bào)排名
    全班排第38名,但是不能說(shuō)參加考試的只有40個(gè)人,反正成績(jī)單上沒寫多少人,相比較之前的40多名還是有進(jìn)步的,哈哈
    這就是加上了一些修飾,我們看看類圖如何修改,如圖17-3:


    17-3

    這應(yīng)該是常規(guī)的處理方式了,直接增加一個(gè)子類,覆寫report方法,我們看具體的實(shí)現(xiàn),代碼如下:

public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
    private void reportHighScore(){
        System.out.println("這次考試語(yǔ)文最高是75,數(shù)學(xué)是78,自然是80");
    }
    private void reportSort(){
        System.out.println("我是排名第38名。。。");
    }
    @Override
    public void report(){
        this.reportHighScore();
        super.report();
        this.reportSort();
    }
}

通過(guò)繼承確實(shí)能夠解決這個(gè)問(wèn)題,但是這個(gè)只能作為應(yīng)急的一種方式,如果需要繼續(xù)加裝飾那怎么辦,而且裝飾條件不是一次就明確的,可能過(guò)一段時(shí)間加一個(gè),那一直通過(guò)繼承來(lái)處理肯定是不合理的。在面向?qū)ο蟮脑O(shè)計(jì)中,如果超過(guò)兩層,是不是該考慮設(shè)計(jì)的問(wèn)題了,這是經(jīng)驗(yàn),不是絕對(duì),繼承的層次越多以后的維護(hù)成本越多,那這么解決這個(gè)裝飾的問(wèn)題呢,我們專門定義一批負(fù)責(zé)裝飾的類,然后根據(jù)實(shí)際情況來(lái)決定是否需要進(jìn)行裝飾,類圖如17-4:


17-4

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

public abstract class Decorator extends SchoolReport {
    //首先得知道是哪個(gè)成績(jī)單
    private SchoolReport schoolReport;
    public Decorator(SchoolReport schoolReport){
        this.schoolReport = schoolReport;
    }
    @Override
    public void report(){
        this.schoolReport.report();
    }
    @Override
    public void sign(String name){
        this.schoolReport.sign(name);
    }
}

裝飾類還是把動(dòng)作的執(zhí)行委托給需要裝飾的對(duì)象,Decorator抽象類的目的很簡(jiǎn)單,就是要讓子類來(lái)封裝SchoolReport的子類,先看HighScoreDecorator實(shí)現(xiàn)類,代碼如下:

public class HighScoreDecorator extends Decorator {

    public HighScoreDecorator(SchoolReport schoolReport) {
        super(schoolReport);
    }
    private void reportHighScore(){
        System.out.println("這次考試語(yǔ)文最高是75,數(shù)學(xué)是78,自然是80");
    }

    @Override
    public void report() {
        this.reportHighScore();
        super.report();
    }
}

重寫了report方法,先調(diào)用具體裝飾類的裝飾方法reportHighScore,然后再調(diào)用具體構(gòu)件的方法,我們?cè)賮?lái)看怎么匯報(bào)學(xué)校排序情況SortDecorator代碼,代碼如下:

public class SortDecorator extends Decorator {
    public SortDecorator(SchoolReport schoolReport) {
        super(schoolReport);
    }
    private void reportSort(){
        System.out.println("我是排名第38名。。。");
    }
    @Override
    public void report(){
        super.report();
        this.reportSort();
    }
}

通過(guò)這兩個(gè)強(qiáng)力的修飾工具,然后就可以拿一份"漂亮"的成績(jī)單出來(lái)了,代碼如下:

public class Client  {
    public static void main(String[] args) {
        SchoolReport schoolReport;
        schoolReport = new FouthGradeSchoolReport();
        schoolReport = new HighScoreDecorator(schoolReport);
        schoolReport = new SortDecorator(schoolReport);
        schoolReport.report();
        schoolReport.sign("老三");
    }
}

這就是裝飾者模式,可以處理一些用繼承來(lái)處理的問(wèn)題,但是比繼承更加的靈活,但是裝飾過(guò)多層之后就不太友好了,就像繼承過(guò)多之后可讀性就變的很差了。

  1. 裝飾者模式的定義

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


17-5

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

  • Component抽象構(gòu)件
    Component是一個(gè)接口或者是抽象類,就是定義我們最核心的對(duì)象,也就是最原始的對(duì)象,如上面的成績(jī)單。
    注意 在裝飾模式中,必然有一個(gè)最基本、最核心、最原始的接口或抽象類充當(dāng) Component抽象構(gòu)件(這個(gè)抽象構(gòu)件應(yīng)該是描述我們想要裝飾的部分
  • ConcreteComponent具體構(gòu)件
    ConcreteComponent是最核心、最原始、最基本的接口或抽象類的實(shí)現(xiàn),就是我們要裝飾的類,就是對(duì)應(yīng)的例子中的FouthGradeSchoolReport
    -Decorator裝飾角色
    一般是一個(gè)抽象類,實(shí)現(xiàn)接口或抽象方法,在他的屬性里必然有一個(gè)private變量指向Component抽象構(gòu)件。(裝飾者模式的核心,和被裝飾者實(shí)現(xiàn)或繼承相同抽象,然后私有屬性指向被裝飾者
    -具體裝飾角色
    ConcreteDecoratorA和ConcreteDecoratorB是兩個(gè)具體的裝飾類,這個(gè)就是用來(lái)裝飾的了,想從哪個(gè)方面哪個(gè)角度去裝飾,就實(shí)現(xiàn)Decorator去寫自己的裝飾方法就好了。
    我們來(lái)看一下裝飾者模式的通用代碼:
public abstract class Component {
    public abstract void operation();
}
public class ConcreateComponent extends Component {
    @Override
    public void operation() {
        //需要被裝飾的方法
        System.out.println("do something");
    }
}
public class Decorator extends Component {
    private Component component;
    public Decorator(Component component){
        this.component = component;
    }
    @Override
    public void operation() {
        //委托給裝飾者執(zhí)行
        component.operation();
    }
}
public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {
        super(component);
    }

    private void method(){
        //do something
    }

    @Override
    public void operation() {
        this.method();
        super.operation();
    }
}

(這個(gè)裝飾者模式和代理模式非常相近,而且都是用來(lái)增強(qiáng)被裝飾類(被代理類)的;但是這兩者的具體的區(qū)別和用法又是怎么樣的呢)

  1. 裝飾者模式應(yīng)用

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

  • 裝飾類和被裝飾類可以獨(dú)立發(fā)展,而不會(huì)互相耦合。換句話說(shuō),Component類無(wú)須知道Decorator類,Decorator類是從外部來(lái)擴(kuò)展Component類的功能,而Decorator也不用知道具體的構(gòu)建。
  • 裝飾者模式是繼承模式的一個(gè)替代方案。我們看裝飾類Decorator,不管裝飾多少層,返回的對(duì)象還是Component,實(shí)現(xiàn)的還是is-a的關(guān)系。
  • 裝飾模式可以動(dòng)態(tài)地?cái)U(kuò)展一個(gè)實(shí)現(xiàn)類的功能,而且還不會(huì)影響到被擴(kuò)展的類,非常符合開閉原則
    3.2裝飾模式的缺點(diǎn)
    裝飾者模式雖然可以很好的替代繼承的方式處理問(wèn)題,并且比繼承更加靈活 ,但是也同樣帶來(lái)了和繼承類似的問(wèn)題,那就是裝飾層數(shù)過(guò)多的問(wèn)題,這個(gè)就只能靈活處理;
    3.3 裝飾模式的使用場(chǎng)景
  • 需要擴(kuò)展一個(gè)類的功能,或給一個(gè)類增加附加功能;
  • 需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能還可以動(dòng)態(tài)的撤銷;
  • 需要為一批的兄弟類進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式
  1. 最佳實(shí)踐

裝飾模式是對(duì)繼承的有力補(bǔ)充,而且裝飾模式比繼承更加的靈活,易維護(hù),易擴(kuò)展,衣服用;這個(gè)之前學(xué)習(xí)例子的時(shí)候就有很好的體會(huì)。
這里有一點(diǎn)就是裝飾模式和代理模式的對(duì)比,這一章中并沒有提出,有興趣可以百度一下;我們學(xué)完23種設(shè)計(jì)模式后,會(huì)接著學(xué)后續(xù)的相似的設(shè)計(jì)模式之前的對(duì)比。

內(nèi)容來(lái)自《設(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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