裝飾器模式主要對現有的類對象進行包裹和封裝,以期望在不改變類對象及其類定義的情況下,為對象添加額外功能。是一種對象結構型模式。需要註意的是,該過程是通過調用被包裹之後的對象完成功能添加的,而不是直接修改現有對象的行為,相當於增加了中間層。類似於python中的@裝飾器。 下麵還是按照老規矩,先來瞭解 ...
裝飾器模式主要對現有的類對象進行包裹和封裝,以期望在不改變類對象及其類定義的情況下,為對象添加額外功能。是一種對象結構型模式。需要註意的是,該過程是通過調用被包裹之後的對象完成功能添加的,而不是直接修改現有對象的行為,相當於增加了中間層。類似於python中的@裝飾器。
下麵還是按照老規矩,先來瞭解一下該模式相關的概念和原理,然後通過兩個具體的實例體會一下如何在實際開發中應用該模式。
1. 目的
可以動態的為同一類的不同對象加以修飾以添加新的功能。
2. 動機
靈活的對類對象功能進行擴展。
3. 優缺點
優點:
- 相比較於類的繼承來擴展功能,對對象進行包裹更加的靈活;
- 裝飾類和被裝飾類相互獨立,耦合度較低;
缺點:
- 沒有繼承結構清晰;
- 包裹層數較多時,難以理解和管理;
4. 應用場景
- 動態的增加對象的功能;
- 不能以派生子類的方式來擴展功能;
- 限制對象的執行條件;
- 參數控制和檢查等;
5. 原理
下麵是GoF介紹的典型的裝飾器模式的UML類圖:
Component:
對象的介面類,定義裝飾對象和被裝飾對象的共同介面;
ConcreteComponent:
被裝飾對象的類定義;
Decorator:
裝飾對象的抽象類,持有一個具體的被修飾對象,並實現介面類繼承的公共介面;
ConcreteDecorator:
具體的裝飾器,負責往被裝飾對象添加額外的功能;
說明:
由於這個模式從實際的例子來理解更加的直觀方便,因此這裡不再單獨的實現上面的UML結構代碼。
6.實例——畫圖
先來通過一個簡單的畫圖的實例來直觀感受一下。
前提:
系統中存在一個畫圓的類,該類只是用來畫圓,以及其他一些大小和位置等參數的控制。
新加需求:
- 可以對圓的邊進行著色
- 可以對圓填充顏色;
- 可以同時對邊和內部著色;
這個需求的常規方法實現可能如下:
- 對畫圓類進行迭代,以支持邊和內部顏色填充 ;
- 畫圓類作為父類,分別定義三個子類,繼承父類的畫圓方法,子類分別實現對應的作色需求;
上面的兩個方法都是可行的,也是比較直觀的,這裡我們嘗試使用裝飾器模式來實現,作為以上兩種方法的對比。
下麵來看一下裝飾器模式實現該需求的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中@裝飾器不同的是:
- python中的裝飾器是作用於函數或者類定義的,並直接覆蓋掉了原來函數或者類的定義;
- 裝飾器模式僅僅是修改了了已經產生的對象的行為,和類定義沒有半點關係;
通過上面的兩個例子,應該對裝飾器模式有了一個簡單的認識。
另外,要體會到什麼時候用繼承什麼時候用裝飾器。
參考:
GoF《Design Patterns: Elements of Reusable Object-Oriented Software》
https://www.runoob.com/design-pattern/decorator-pattern.html