設計模式之裝飾器模式(decorator pattern)

裝飾器模式主要對現有的類對象進行包裹和封裝,以指望在不改變類對象及其類定義的狀況下,爲對象添加額外功能。是一種對象結構型模式。須要注意的是,該過程是經過調用被包裹以後的對象完成功能添加的,而不是直接修改現有對象的行爲,至關於增長了中間層。相似於python中的@裝飾器。html

下面仍是按照老規矩,先來了解一下該模式相關的概念和原理,而後經過兩個具體的實例體會一下如何在實際開發中應用該模式。python

1. 目的

能夠動態的爲同一類的不一樣對象加以修飾以添加新的功能。網絡

2. 動機

靈活的對類對象功能進行擴展。app

3. 優缺點

優勢:tcp

  1. 相比較於類的繼承來擴展功能,對對象進行包裹更加的靈活;
  2. 裝飾類和被裝飾類相互獨立,耦合度較低;

缺點:ide

  1. 沒有繼承結構清晰;
  2. 包裹層數較多時,難以理解和管理;

4. 應用場景

  • 動態的增長對象的功能;
  • 不能以派生子類的方式來擴展功能;
  • 限制對象的執行條件;
  • 參數控制和檢查等;

5.  原理

下面是GoF介紹的典型的裝飾器模式的UML類圖:函數

Component:this

 對象的接口類,定義裝飾對象和被裝飾對象的共同接口;spa

ConcreteComponent:code

 被裝飾對象的類定義;

Decorator:

 裝飾對象的抽象類,持有一個具體的被修飾對象,並實現接口類繼承的公共接口;

ConcreteDecorator:

 具體的裝飾器,負責往被裝飾對象添加額外的功能;

說明:

 因爲這個模式從實際的例子來理解更加的直觀方便,所以這裏再也不單獨的實現上面的UML結構代碼。

6.實例——畫圖

先來經過一個簡單的畫圖的實例來直觀感覺一下。

前提:

系統中存在一個畫圓的類,該類只是用來畫圓,以及其餘一些大小和位置等參數的控制。

新加需求:

  • 能夠對圓的邊進行着色
  • 能夠對圓填充顏色;
  • 能夠同時對邊和內部着色;

這個需求的常規方法實現可能以下:

  1. 對畫圓類進行迭代,以支持邊和內部顏色填充 ;
  2. 畫圓類做爲父類,分別定義三個子類,繼承父類的畫圓方法,子類分別實現對應的做色需求;

上面的兩個方法都是可行的,也是比較直觀的,這裏咱們嘗試使用裝飾器模式來實現,做爲以上兩種方法的對比。

下面來看一下裝飾器模式實現該需求的UML類圖:

接口類:shape

public interface Shape {
    void draw();
}

 畫圓類:Circle

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.print("a circle!");
    }
}

抽象裝飾器類:Decorator

public abstract class Decorator implements Shape {
    
    protected Shape circle;
    
    public Decorator(Shape shape) {
        circle = shape;
    } 
    
    public void draw() {
        circle.draw();
    }
}

爲圓邊着色裝飾器類:CircleEdge

public class CircleEdge extends Decorator {
    
    public CircleEdge(Shape circle) {
        super(circle);
    }
    
    private void setEdgeColor() {
        System.out.print(", edge with color");
    }
    
    public void draw() {
        circle.draw();
        setEdgeColor();
    }
}

爲圓填充顏色裝飾器類:CircleEdge

public class CircleFill extends Decorator {
    
    public CircleFill(Shape circle) {
        super(circle);
    }
    
    private void setEdgeFill() {
        System.out.print(", content with color");
    }
    
    public void draw() {
        circle.draw();
        setEdgeFill();
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Shape circle = new Circle();
        circle.draw();
        System.out.println("");
        Decorator circleEdge = new CircleEdge(circle);
        circleEdge.draw();
        System.out.println("");
        Decorator circleFill = new CircleFill(circle);
        circleFill.draw();
        System.out.println("");
        Decorator circleEdgeFill = new CircleFill(circleEdge);
        circleEdgeFill.draw();
    }
}

結果:

a circle!
a circle!, edge with color
a circle!, content with color
a circle!, edge with color, content with color

 上面咱們經過實現兩個裝飾器分別完成對邊着色和填充的需求,經過對裝飾器的進一步裝飾,咱們完成了同時着色的需求。

7.實例——網絡數據報封裝

接下來咱們在使用網絡數據傳輸的例子來體會一下裝飾器模式,下圖表示的是應用層的文件傳輸協議FTP經過TCP來傳輸數據:

雖然應用層能夠越過傳輸層直接使用網絡層進行數據發送(如,ICMP),但多數都會使用傳輸層的TCP或者UDP進行數據傳輸的。

下面咱們用裝飾器模式來表示一下應用層數據經過傳輸層來發送數據,UML類圖以下:

上述圖中表示了,應用層的數據經過添加TCP頭或者UDP頭,而後經過下面的網絡層send數據。

 數據報接口類:Datagram

public interface Datagram {
    void send();    // 經過網絡層發送IP數據報
}

應用層數據類:AppDatagram

public class AppDatagram implements Datagram {
    @Override
    public void send() {
        System.out.print("send IP datagram!");
    }
}

傳輸層類(抽象裝飾器):TransportLayer

public abstract class TransportLayer implements Datagram {
    
    protected Datagram appData;
    
    public TransportLayer(Datagram appData) {
        this.appData = appData;
    } 
    
    public void send() {
        appData.send();
    }
}

添加TCP頭部類:UseTCP

public class UseTCP extends TransportLayer {
    
    public UseTCP(Datagram appData) {
        super(appData);
    }
    
    private void addHeader() {
        System.out.print("Appdata add TCP header, ");
    }
    
    public void send() {
        addHeader();
        appData.send();
    }
}

添加TCP頭部類:UseUDP

public class UseUDP extends TransportLayer {
    
    public UseUDP(Datagram appData) {
        super(appData);
    }
    
    private void addHeader() {
        System.out.print("Appdata add UDP header, ");
    }
    
    public void send() {
        addHeader();
        appData.send();
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Datagram appData = new AppDatagram();
        appData.send();
        System.out.println("");
        TransportLayer tcpData = new UseTCP(appData);
        tcpData.send();
        System.out.println("");
        TransportLayer udpData = new UseUDP(appData);
        udpData.send();
        System.out.println("");
    }
}

結果:

send IP datagram!
Appdata add TCP header, send IP datagram!
Appdata add UDP header, send IP datagram!

固然這裏例子中已經添加過TCP頭部的數據報不能再使用UDP傳輸了,無心義,也被必要。

8. 總結

其實所謂裝飾器,本質上是對現有類對象的包裹,獲得一個增強版的對象。

和python中@裝飾器不一樣的是:

  1. python中的裝飾器是做用於函數或者類定義的,並直接覆蓋掉了原來函數或者類的定義;
  2. 裝飾器模式僅僅是修改了了已經產生的對象的行爲,和類定義沒有半點關係;

經過上面的兩個例子,應該對裝飾器模式有了一個簡單的認識。

另外,要體會到何時用繼承何時用裝飾器。

參考:

GoF《Design Patterns: Elements of Reusable Object-Oriented Software》

https://www.runoob.com/design-pattern/decorator-pattern.html

相關文章
相關標籤/搜索