23種設計模式(三)--裝飾器模式

盛開的太陽發表於2021-06-22

裝飾器模式

一. 什麼是裝飾器模式?

我們都知道裝飾, 元旦, 聖誕節, 我們都需要裝飾, 渲染節日氣氛. . 所謂裝飾, 就是在原來的基礎上加東西.

裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是對現有類的包裝。

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的例項來演示裝飾器模式的用法。其中,我們將把聖誕節的房間在原來的基礎上裝飾上了氣球。

我們來看看UML圖:
最開始有房子, 房子有平房, 樓房, 別墅, 我們會打掃房間.

/**
 * 房子抽象類
 */
public interface IHouse {
    void clean();
}

/**
 * 平房
 */
public class Bungalow implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
    }
}

/**
 * 樓房
 */
public class HighRiseBuilding implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
    }
}

/**
 * 別墅
 */
public class Villa implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
    }
}

這時, 遇到節假日, 比如:聖誕節, 元旦, 我們會裝飾房間, 給房間佈置氣球等.
我們可以採用裝飾器設計模式. 這時, 裝飾的主體依然房子. 裝飾就像是一個殼子, 套在房子的外面.

23種設計模式(三)--裝飾器模式
/**
 * 房子抽象類
 */
public interface IHouse {
    void clean();
}

/**
 * 平房
 */
public class Bungalow implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
        System.out.println("我是平房: 打掃房間");
    }
}

/**
 * 樓房
 */
public class HighRiseBuilding implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
        System.out.println("我是樓房: 打掃房間");
    }
}

/**
 * 別墅
 */
public class Villa implements IHouse{
    @Override
    public void clean() {
        // 打掃房間
        System.out.println("我是別墅: 打掃房間");
    }
}

/**
 * 房間裝飾器
 */
public abstract class DecoratorHouse implements IHouse {
    // 被裝飾的房子
    protected IHouse decoratedHouse;

    public DecoratorHouse(IHouse decoratedHouse) {
        this.decoratedHouse = decoratedHouse;
    }

}

public class BalloonDecoratorHouse extends DecoratorHouse {


    public BalloonDecoratorHouse(IHouse decoratedHouse) {
        super(decoratedHouse);
    }

    @Override
    public void clean() {
        // 打掃房間
        super.decoratedHouse.clean();
        ballonDecorate();
    }

    public void ballonDecorate() {
        // 用氣球裝飾房間
        System.out.println("用氣球裝飾房間, 好漂亮");
    }
}

public class DecoratorPatternTest {
    public static void main(String[] args) {
        IHouse house = new Villa();
        house.clean();

        System.out.println("-------聖誕節到了, 裝飾房間-------");
        IHouse decoratorHouse = new BalloonDecoratorHouse(new Villa());
        decoratorHouse.clean();
    }
}

使用裝飾器模式以後, 我們在不改變原有房子的基礎上進行了擴充套件. 節假日, 就可以使用裝飾類裝飾房間, 如果節日過去了, 我們就繼續使用原來的房子.

接下來, 我們來總結一些上面的案例.
首先, 我們有一個元件Component, 在這個元件裡有一些自定義的功能. 通常這個元件Component是抽象的(介面或者抽象類)
然後, 抽象類會有一個具體的實現 ConcreteComponent, 除了實現元件Component的方法, 還可自定義方法.
第三, 我們現在想要對具體實現ConcreteComponent進行包裝, 定義一個包裝類 DecoratorComponent. 通常這個包裝類是抽象的. 包裝類也實現元件Component介面, 然後引入一個Component的具體的成員變數. 為什麼要引入成員變數呢? 這個也很好理解, 我們的目標是包裝的具體類.
第四, 定義一個具體的裝飾器 DecoratorComponent, 再具體的裝飾器中, 可以增加額外的方法. 比如在之前後者之後增加一些邏輯. 具體關係圖如下:

23種設計模式(三)--裝飾器模式

程式碼實現如下:

/**
 * 抽象功能
 */
public interface Component {
    void operate();
}

/**
 * 具體功能實現
 */
public class ConcreteComponent implements Component{
    @Override
    public void operate() {
        // 實現具體的邏輯
        System.out.println("具體實現的邏輯");
    }
}

/**
 * 用來裝飾Component物件的類
 */
public abstract class DeceratorComponent implements Component{
    // 指定裝飾的物件
    protected Component deceratedComponent;

    public DeceratorComponent(Component deceratedComponent) {
        this.deceratedComponent = deceratedComponent;
    }
}

/**
 * 具體的裝飾類
 */
public class ConcreteDeceratorComponent extends DeceratorComponent {

    public ConcreteDeceratorComponent(Component deceratedComponent) {
        super(deceratedComponent);
    }

    @Override
    public void operate() {
        before();
        super.deceratedComponent.operate();
        after();
    }

    public void before(){
        System.out.println("在原邏輯之前增加了邏輯");
    }

    public void after(){
        System.out.println("在原邏輯之後增加了邏輯");
    }

    
}

public class DeceratorTest {
    public static void main(String[] args) {
        Component concreteCom = new ConcreteComponent();
        concreteCom.operate();

        System.out.println("============");
        DeceratorComponent decerator = new ConcreteDeceratorComponent(new ConcreteComponent());
        decerator.operate();

    }
}

執行結果:

具體實現的邏輯

============

在原邏輯之前增加了邏輯
具體實現的邏輯
在原邏輯之後增加了邏輯

設計模式的靈活應用. 假如當前具體類就只有一個. 我們就不需要定義抽象的Component了. 如何實現裝飾模式呢?
那就讓裝飾器直接繼承自原來的類就可以了:
23種設計模式(三)--裝飾器模式

二. 裝飾器模式的特點:

  1. 裝飾物件和真實物件有相同的介面。這樣客戶端物件就能以和真實物件相同的方式和裝飾物件互動。
  2. 裝飾物件包含一個真實物件的引用(reference)
  3. 裝飾物件接受所有來自客戶端的請求。它把這些請求轉發給真實的物件。
  4. 裝飾物件可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在執行時,不用修改給定物件的結構就可以在外部增加附加的功能。在物件導向的設計中,通常是通過繼承來實現對給定類的功能擴充套件。

三. 裝飾器模式的使用場景:

以下情況可以考慮使用裝飾器模式

  1. 需要擴充套件一個類的功能,或給一個類新增附加職責。
  2. 需要動態的給一個物件新增功能,這些功能可以再動態的撤銷。
  3. 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。
  4. 當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。

四. 裝飾器模式的優點:

  1. Decorator模式與繼承關係的目的都是要擴充套件物件的功能,但是Decorator可以提供比繼承更多的靈活性。
  2. 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。

五. 裝飾器模式的缺點

  1. 這種比繼承更加靈活機動的特性,也同時意味著更加多的複雜性。
  2. 裝飾模式會導致設計中出現許多小類,如果過度使用,會使程式變得很複雜。
  3. 裝飾模式是針對抽象元件(Component)型別程式設計。但是,如果你要針對具體元件程式設計時,就應該重新思考你的應用架構,以及裝飾者是否合適。當然也可以改變Component介面,增加新的公開的行為,實現“半透明”的裝飾者模式。在實際專案中要做出最佳選擇。

六. 思考: 裝飾器模式使用了哪些設計模式的原則?

  1. 最明顯的體現就是開閉原則---對擴充套件開發, 對修改關閉. 當新的需求到達, 在不改變原來功能的基礎上進行擴充套件.
  2. 依賴倒置原則---依賴於抽象, 而非具體. 方便擴充套件, 再來一種新房子, 不用修改房子裝飾類.
  3. 單一職責原則: 一個類只負責一件事

相關文章