Decorator(裝飾器)模式


Decorator模式思維導圖

描述

定義

動態地給一個對象添加一些額外的職責,其別名爲包裝器(Wrapper)。就增加對象功能來說,裝飾器模式比生成子類實現更爲靈活。

類型

對象結構型模式

UML類圖

Decorator模式UML類圖

時序圖

Decorator模式時序圖

特點

  • 裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案。
  • 大多數的裝飾模式實際上是半透明的裝飾模式,即允許增加新的方法。半透明的裝飾模式是介於裝飾器模式和適配器模式之間的。

實現

主要角色

  • Component:抽象接口,以規範準備接收附加責任的類;
  • ConcreteComponent:目標實現類,定義一個將要接收附加責任的實現類;
  • Decorator:裝飾器抽象類,持有一個構件(Component)對象的實例,將請求轉發給它的Component對象(可能在轉發請求前後執行一些附加的動作),實現一個與抽象構件接口一致的接口。
  • ConcreteDecorator:裝飾器實現類,負責給構件對象“貼上”附加的責任;

注意事項

  • 接口的一致性:裝飾對象的接口必須與它所裝飾的Component的接口是一致的;
  • 省略抽象的Decorator類:當你僅需要添加一個職責時,沒有必要定義抽象Decorator類,這時可以把Decorator合併到ConcreteDecorator中;
  • 保持Component類的簡單性:爲了保證接口的一致性,組件和裝飾必須有一個公共的Component父類,保持公共父類的簡單性是很重要的;
  • 改變對象外殼與改變對象內核:將Decorator看作一個對象的外殼,它可以改變這個對象的行爲。另外一種方法是改變對象的內核,如Strategy模式。

示例

  • Component:抽象接口

      interface Component {
          void operation();
      }
    
  • ConcreteComponent:目標實現類

      public class ConcreteComponent implements Component {
          @Override
          public void operation() {
              System.out.println("目標實現類!");
          }
      }
    
  • Decorator:裝飾器抽象類

      public abstract class Decorator implements Component{
          private Component component;
          public Decorator(Component component){
              this.component = component;
          }
      
          @Override
          public void operation() {
              component.operation();
          } 
      }
    
  • ConcreteDecorator:裝飾器實現類

      public class ConcreteDecoratorA extends Decorator {
          public ConcreteDecoratorA(Component component) {
              super(component);
          }
          
          @Override
          public void operation() {
      		 // preOperation
           super.operation();
               // postOperation
          }
      }
    
      public class ConcreteDecoratorB extends Decorator {
          public ConcreteDecoratorB(Component component) {
              super(component);
          }
          
          @Override
          public void operation() {
            // preOperation
           super.operation();
               // postOperation
          }
      }
    
  • Client:客戶類

      public class Client {
          public static void main(String[] args) {
              Component component = new ConcreteComponent();
      		Component decorator = new ConcreteDecoratorB(new ConcreteDecoratorA(component));
              decorator.operation();
          }
      }
    

適用場景

  • 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
  • 需要動態地給一個對象增加功能,並且這些功能也可以動態地被撤銷。
  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴展和維護時。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因爲類定義不能繼承(如final類)。

優點

  • 比靜態繼承更靈活:與對象的靜態繼承(多重繼承)相比,Decorator模式提供了更加靈活的向對象添加職責的方式。可以用添加和分離的方法,用裝飾在運行時刻增加和刪除職責。相比之下,繼承機制要求爲每個添加的職責創建一個新的子類,這會產生許多新的類,並且會增加系統的複雜度。
  • 可以通過一種動態的方式來擴展一個對象的功能,如通過配置文件可以在運行時選擇不同的裝飾器,從而實現不同的行爲。
  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行爲的組合,從而得到功能更爲強大的對象。
  • 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”。

缺點

  • Decorator與它的ConcreteComponent不一樣:Decorator是一個透明的包裝。如果我們從對象標識的觀點出發,一個被裝飾了的組件與這個組件是有差別的,因此,使用裝飾時不應該依賴對象標識。
  • 有許多小對象:採用Decorator模式進行系統設計往往會產生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同,而不是它們的類或是它們的屬性值有所不同。同時還將產生很多具體裝飾類。這些裝飾類和小對象的產生將增加系統的複雜度,加大學習與理解的難度。
  • 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易於出錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較爲煩瑣。

相關模式

  • Adapter模式:適配器模式將給對象一個全新的接口,而裝飾僅改變對象的職責而不改變它的接口。
  • Composite模式:可以將裝飾視爲一個退化的、僅有一個組件的組合。然而,裝飾僅給對象添加一些額外的職責——它的目的不在於對象聚焦。
  • Strategy模式:改變對象的途徑不同,裝飾可以改變對象的外殼,而Strategy模式可以改變對象的內核。
  • Bridge模式:橋接中的行爲是橫向的行爲,行爲彼此之間無關聯,就比如異常和異常處理之間是沒有行爲關聯的一樣;而裝飾者模式中的行爲具有可疊加性,其表現出來的結果是一個整體,各個行爲組合後的一個結果。

實際應用

裝飾模式在Java語言中的最著名的應用莫過於Java I/O標準庫的設計了。由於Java I/O庫需要很多性能的各種組合,如果這些性能都是用繼承的方法實現的,那麼每一種組合都需要一個類,這樣就會造成大量性能重複的類出現。而如果採用裝飾模式,那麼類的數目就會大大減少,性能的重複也可以減至最少。

InputStream UML類圖

根據上圖可以看出:

  • 抽象構件(Component)角色:由InputStream扮演。這是一個抽象類,爲各種子類型提供統一的接口。
  • 具體構件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實現了抽象構件角色所規定的接口。
  • 抽象裝飾(Decorator)角色:由FilterInputStream扮演。它實現了InputStream所規定的接口。
  • 具體裝飾(ConcreteDecorator)角色:由幾個類扮演,分別是BufferedInputStream、DataInputStream以及兩個不常用到的類LineNumberInputStream、PushbackInputStream。

下面是使用I/O流讀取文件內容的簡單操作示例。

public class Client {
    public static void main(String[] args) throws IOException {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")
                    )
            );
            //讀取文件內容
            byte[] bs = new byte[dis.available()];
            dis.read(bs);
            String content = new String(bs);
            System.out.println(content);
        } finally{
            dis.close();
        }
    }

}

觀察上面的代碼,會發現最裏層是一個FileInputStream對象,然後把它傳遞給一個BufferedInputStream對象,經過BufferedInputStream處理,再把處理後的對象傳遞給了DataInputStream對象進行處理,這個過程其實就是裝飾器的組裝過程,FileInputStream對象相當於原始的被裝飾的對象,而BufferedInputStream對象和DataInputStream對象則相當於裝飾器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章