【趣味設計模式系列】之【裝飾器模式】

小豬爸爸發表於2020-09-13

1. 簡介

裝飾器模式(Decorator Pattern):動態地給一個物件新增職責,就增加功能來說,裝飾器比生成子類更靈活。

2. 示例

水果店需要給網上客戶發貨,除了包裝之外,需要對特定水果包裝加額外裝飾,比如加防偽標誌、加固、加急等額外功能,但在外部看來還是打包元件。

類圖設計

水果包裝介面類Bag,介面方法pack,完成水果打包。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:13
 * @Desc: 包裝介面
 */
public interface Bag {
    void pack();
}

蘋果、橘子、香蕉各自實現包裝介面,用自己的特定的外包裝。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 蘋果包裝類
 */
public class AppleBag implements Bag {
    @Override
    public void pack() {
        System.out.println("蘋果使用紙箱包裝");
    }
}

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 橘子包裝類
 */
public class OrangeBag implements Bag {
    @Override
    public void pack() {
        System.out.println("橘子使用網兜包裝");
    }
}

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 香蕉包裝類
 */
public class BananaBag implements Bag {
    @Override
    public void pack() {
        System.out.println("香蕉使用竹籮包裝");
    }
}

裝飾器類BagDecorator,實現包裝類Bag介面,同時擁有包裝介面的引用,為了組裝更多具體裝飾器加入進來,增加包裝類的裝飾功能。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:23
 * @Desc: 裝飾器類
 */
public class BagDecorator implements Bag {
    private Bag bag;  //維持一個對抽象構件物件的引用

    public BagDecorator(Bag bag)  //注入一個抽象構件型別的物件
    {
        this.bag = bag;
    }

    public void pack() {
        bag.pack();
    }
}

防偽裝飾器CheckedBagDecorator,繼承BagDecorator類,並增加自己的防偽標識方法checked。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:32
 * @Desc: 防偽裝飾器
 */
public class CheckedBagDecorator extends BagDecorator {
    public CheckedBagDecorator(Bag bag) {
        super(bag);
    }

    @Override
    public void pack() {
        super.pack();
        checked();  //列印防偽標識
    }

    //增加防偽標識
    public void checked() {
        System.out.println("===============");
        System.out.println("列印上防偽標識");
    }
}

加固裝飾器ReinforceBagDecorator,繼承BagDecorator類,並增加自己的加固方法reinforce。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:34
 * @Desc: 加固裝飾器
 */
public class ReinforceBagDecorator extends BagDecorator{
    public ReinforceBagDecorator(Bag bag) {
        super(bag);
    }

    public void pack() {
        super.pack();  //呼叫原有業務方法
        reinforce();
    }

    //加固包裝
    public void reinforce() {
        System.out.println("===============");
        System.out.println("加固了包裝");
    }
}

加急裝飾器SpeedBagDecorator,繼承BagDecorator類,並增加自己的加急方法speedy。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:34
 * @Desc: 加急裝飾器
 */
public class SpeedBagDecorator extends BagDecorator {

    public SpeedBagDecorator(Bag bag) {
        super(bag);
    }

    public void pack() {
        super.pack();  //呼叫原有業務方法
        speedy();
    }

    //快件加急
    public void speedy() {
        System.out.println("===============");
        System.out.println("打上加急標識");
    }
}

客戶端類

package com.wzj.decorator;

import org.aspectj.weaver.ast.Or;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:40
 * @Desc:
 */
public class Client {
    public static void main(String[] args) {
        AppleBag appleBag = new AppleBag();
        OrangeBag orangeBag = new OrangeBag();
        BananaBag bananaBag = new BananaBag();
        // 蘋果紙箱包裝後,外加防偽標識、加固包裝
        new ReinforceBagDecorator(new CheckedBagDecorator(appleBag)).pack();
        System.out.println("*********************************");
        // 橘子網兜包裝後,外加防偽標識、加固包裝
        new SpeedBagDecorator(new ReinforceBagDecorator(new CheckedBagDecorator(orangeBag))).pack();

    }
}

結果

蘋果使用紙箱包裝
===============
列印上防偽標識
===============
加固了包裝
*********************************
橘子使用網兜包裝
===============
列印上防偽標識
===============
加固了包裝
===============
打上加急標識

從上述例子可以看出,裝飾器的好處,不僅可以對具體的水果包裝類進行裝飾,多個裝飾器還可以巢狀裝飾,非常靈活,這也是為什麼,裝飾器中需要引用Bag類,就是方便巢狀,因為每個具體的裝飾器,本身也是Bag的子類。

3. 原始碼分析

Java IO類庫非常龐大,從大類分,如果從按流的方向來分的話,分為輸入流InputStream,輸出流OutputStream,如果按照讀取的方式分的話,分為位元組流與字元流,具體如下圖

針對不同的讀取和寫入場景,Java IO 又在這四個父類基礎之上,擴充套件出了很多子類,如下圖

OutputStream 是一個抽象類,FileOutputStream 是專門用來寫檔案流的子類,FilterOutputStream很特殊,它實現了OutputStream,同時持有OutputStream的引用,部分原始碼如下圖:

public class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;

    ...

所以FilterOutputStream在設計的時候本質就是一個裝飾器,其子類BufferedOutputStream,DataOutputStream,PrintStream都是具體的裝飾器,實現額外的功能。
同樣FilterInputStream、InputStreamReader、OutputStreamWriter都是裝飾器類,其子類充當具體裝飾的功能。
在平時寫程式碼的時候都有如下幾行巢狀的寫法:

File f = new File("c:/work/test.data");
FileOutputStream fos = new FileOutputStream(f);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("https://githup.com");
bw.flush();
bw.close();

本質上,OutputStream的實現類可以與具體裝飾器實現相互巢狀,具體的裝飾器之間也可以相互巢狀,非常靈活,避免了獨立為每個類建立子類而產生累爆炸的不合理設計。

4. 總結

4.1 優點

  • 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
  • 處理可以撤銷的職責。
  • 擴充套件子類靈活,避免產生累爆炸。

4.2 缺點

  • 如果最裡面的裝飾器出錯,需要從外面一層一層往裡面去檢視,多層巢狀導致增加複雜性。

相關文章