不學無數 — 裝飾模式

不學無數的程式設計師發表於2018-09-18

裝飾模式

在開始之前

我們可以用一個簡單的例子引出來裝飾模式,在小的時候,相信大家都有過這樣的經歷:小學每年會有好幾次的考試,如果有一次成績非常差,而且考完以後學校會有個很損的招,就是列印出來成績單,然後讓家長簽字。那麼拿著這個成績單,肯定是不會直接告訴家長成績什麼的,肯定是會加一些,例如,語文考了65,就會說我們班最高的才75。如果成績單沒有排名的話,那麼也會添油加醋的說排名靠前,這樣父母的就會被你說的所迷惑住,忽略了真實的成績。這樣說不定還會賞你點東西,然後大筆一揮一簽字,你也可以鬆一口氣終於又混過一次了。

其實這些東西都可以用類進行表示,類圖如下

成績報告圖

SchoolReport 類如下

abstract class SchoolReport{
    public abstract void report();
    public abstract void sign(String name);
}

複製程式碼

FourGradesSchoolReport類如下

class FourGradesSchoolReport extends SchoolReport{

    @Override
    public void report() {
        System.out.println("尊敬的家長您好:");
        System.out.println("您孩子的成績如下: ");
        System.out.println("語文65 數學70 體育80");
    }

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

複製程式碼

Father類如下

public static void main(String[] args) {
    SchoolReport schoolReport = new FourGradesSchoolReport();
    schoolReport.report();
    schoolReport.sign("張三");
}

複製程式碼

當然這是學習好的同學的做法,直接將成績單展示給家長就好,像考的差的就得加上上面的修飾了。類圖如下:

經過修飾的類圖

上面的兩個類不變,只是增加了修飾的類SougarFourGradesSchoolReport如下:

class SougarFourGradesSchoolReport extends FourGradesSchoolReport{

    private void reportHighScore(){
        System.out.println("這次考試語文最高成績是75,數學最高是80");
    }

    private void reportSort(){
        System.out.println("我在班級排名是20");
    }

    public void report(){
    	  //先彙報最高的成績
        this.reportHighScore();
        super.report();
        //然後彙報排名
        this.reportSort();
    }
}

複製程式碼

就會發現輸出如下

這次考試語文最高成績是75,數學最高是80
尊敬的家長您好:
您孩子的成績如下: 
語文65 數學70 體育80
我在班級排名是20
家長簽名: 張三

複製程式碼

直接通過繼承FourGradesSchoolReport此類確實能解決現有的問題,但是現實情況是非常複雜的,如果是老爸當時喝多了直接看了成績單就直接簽字了、或者是聽完彙報最高成績以後就直接樂的直接簽字了,後面排名啥的都不看了、又或者是老爸想先看排名怎麼辦?繼續擴充套件?那會增加多少類?這還是一個比較簡單的場景,如果需要裝飾的條件非常多的話,那麼每個條件都進行擴充套件的話,那麼子類的數量會激增。而且後期維護也不好。

因此出現問題了,擴充套件性不好該怎麼辦?聰明的程式設計師們想到了一個辦法,專門定義一批負責裝飾的類,然後根據實際的情況進行組裝裝飾。類圖如下:

經過改進的類圖

此時的Decorator類如下

abstract class Decorator extends SchoolReport{
    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);
     }
 }

複製程式碼

此時如果你瞭解代理模式的話,可能會有疑問,這和代理模式不是一樣嗎?帶著這個疑問讀下去。

此時的兩個修飾類HighSoreDecoratorSortDecorator如下

class HighScoreDecorator extends Decorator{

    public HighScoreDecorator(SchoolReport schoolReport) {
        super(schoolReport);
    }

    private void reportHighScore(){
        System.out.println("這次考試語文最高成績是75,數學最高是80");
    }

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

class SortDecorator extends Decorator{

    public SortDecorator(SchoolReport schoolReport) {
        super(schoolReport);
    }

    private void reportSort(){
        System.out.println("我在班級排名是20");
    }

    public void report(){
        this.reportSort();
        super.report();
    }
}

複製程式碼

此時在呼叫的時候就可以進行隨意的裝飾,例如老爸想先聽最高分,然後直接簽名那麼呼叫如下

public static void main(String[] args) {
    SchoolReport schoolReport;
    schoolReport = new FourGradesSchoolReport();
    schoolReport = new HighScoreDecorator(schoolReport);
    schoolReport.report();
    schoolReport.sign("張三");
}

複製程式碼

列印如下

這次考試語文最高成績是75,數學最高是80
尊敬的家長您好:
您孩子的成績如下: 
語文65 數學70 體育80
家長簽名: 張三

複製程式碼

例如老爸想要先聽排名,然後聽最高成績,最後簽名,那麼呼叫如下

public static void main(String[] args) {
    //成績單拿過來
    SchoolReport schoolReport;
    //原裝的成績單
    schoolReport = new FourGradesSchoolReport();
    //加了最高分的成績單
    schoolReport = new HighScoreDecorator(schoolReport);
    //加了成績排名的成績單
    schoolReport = new SortDecorator(schoolReport);
    schoolReport.report();
    schoolReport.sign("張三");
}

複製程式碼

列印如下

我在班級排名是20
這次考試語文最高成績是75,數學最高是80
尊敬的家長您好:
您孩子的成績如下: 
語文65 數學70 體育80
家長簽名: 張三

複製程式碼

此時我們如果想增加其他的裝飾模式,只需要繼承了Decorator類即可,然後在使用的時候盡情組合就行。

裝飾模式定義

裝飾模式是動態的給一個物件新增一些額外的職責,就增加功能來說,裝飾模式相比生成子類更加靈活

裝飾模式的通用類圖如下

裝飾模式的通用類圖

在類圖中有四種角色需要說明

  • Component:Component是一個介面或者是抽象類,即定義我們最核心的物件,也就是最原始的物件,如上面的成績單SchoolReport
  • ConcreateComponent:是最原始最基本的介面或者抽象類的實現,被裝飾的物件
  • Decorator:一般是一個抽象類,實現介面或者抽象方法,它裡面不一定有抽象的方法,但是它的屬性中必然有一個private變數指向Component抽象構件
  • ConcreteDecoratorAConcreteDecoratorB:兩個具體的裝飾類,在裡面需要寫所想裝飾的東西。

那麼接下來看裝飾類通用的實現

Component

abstract class Component{
    public abstract void operate();
}

複製程式碼

具體的實現類ConcreateComponent


class ConcreateComponent extends Component{
    @Override 
    public void operate() {
        System.out.println("do something");
    }
}

複製程式碼

抽象的裝飾類Decorator

abstract class Decorator extends Component {
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        this. component. operate();
    }
}

複製程式碼

具體的裝飾類ConcreteDecoratorAConcreteDecoratorB

class ConcreteDecoratorA extends Decorator{

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

    private void methodA(){
        System.out.println("MethodA裝飾");
    }
    
    public void operate(){
        this.methodA();
        super.operate();
    }
}

class ConcreteDecoratorB extends Decorator{

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

    private void methodB(){
        System.out.println("MethodB裝飾");
    }
    
    public void operate(){
        this. methodB();
        super.operate();
    }
}

複製程式碼

此處需要主要原始方法和裝飾方法的執行順序在具體的裝飾類中時固定的,如果想要不同的順序可以通過過載實現多種執行順序。

裝飾模式的優缺點

優點

  • 裝飾類和被裝飾類可以獨立發展,不會相互耦合,換句話說,Component類無需知道Decorator的類,Decorator類是從外部來擴充套件Component類的功能。
  • 裝飾模式是繼承關係的一個替代方案,我們可以看到在裝飾類中Decorator無論裝飾了多少層,返回的物件還是Component
  • 裝飾模式可以動態的擴充套件一個實現類的功能

缺點

只需要記住一點就好:複雜

裝飾模式和代理模式的區別

相信前面看完了裝飾模式會對裝飾模式有個簡單的理解,裝飾模式以對客戶透明的方式擴充套件物件功能,主要凸顯的是修飾、增加功能

而代理模式是給物件提供一個代理物件,並由代理物件來控制原有物件的引用,主要凸顯是控制功能。

參考文章

相關文章