裝飾者模式是一種用於替代繼承的技術,它通過一種無須定義子類的方式給物件動態增加職責,使物件之間的關聯關係取代類之間的繼承關係。
模式動機
裝飾者可以在不改變一個物件本身的基礎上給物件增加額外的新行為,如一張照片,可以不改變照片本身,給她增加一個相框,使得它具有防潮功能,而且使用者可以根據需要增加不同型別的相框。在軟體開發中,類似給照片增加相框的情況隨處可見,如給一個圖形介面構件增加邊框、滾動等新特性。一般有兩種方式實現給一個類或物件增加行為:
-
繼承機制
通過繼承一個類現有類可以使子類在擁有自身方法的同時還擁有父類方法。但這種方式是靜態的,使用者不能控制增加行為的方式和時機。
-
關聯機制
將一個類的物件嵌入另一個新物件中,由另一個物件來決定是否呼叫嵌入物件的行為並擴充套件自己的行為,我們稱這個新物件(即另一個物件)為裝飾類(Dectorator)。
模式定義
動態地給一個物件增加一些額外的職責(Responsibility),就增加物件功能來說,裝飾者模式比生成子類物件實現更靈活。其別名也可以稱為包裝器(Wrapper)。
模式結構
-
Component(抽象構件)
抽象構件定義了物件的介面,可以給這些物件動態增加職責(方法)。抽象構件是具體構件和和抽象裝飾類的共同父類,宣告瞭在具體構件中實現的業務方法。
-
ConcreteComponent(具體構件)
具體構件定義了具體構件物件,實現在抽象構件中宣告的方法。
-
Decorator(抽象裝飾類)
抽象裝飾類是抽象構件類的子類,用於給具體構件增加職責,但具體職責在其子類中實現。它維護一個指向抽象構件物件的引用,通過該引用呼叫裝飾之前構件物件的方法,並通過子類擴充套件該方法。
-
ConcreteDecorator(具體裝飾類)
具體裝飾類是抽象裝飾類的子類,負責向構件新增新的職責。每一個具體裝飾類都定義了一些新行為,它可以呼叫在抽象裝飾類中定義的方法,並增加新的方法以擴充物件的行為。
例項之多重加密系統
某系統提供一個資料加密功能,可以對字串進行加密。該系統分別提供了簡單的移位加密演算法、稍複雜的逆向輸出加密和更高階的求模加密。使用者先使用最簡單的移位加密演算法對字串進行加密,如果覺得不夠可以對加密後結果進行二次乃至三次加密。
-
抽象構件類 Cipher(抽象加密類)
public interface Cipher { //方法為待加密字串,返回值為加密後密文 public String encrypt(String plantTetx); }
-
具體構件類 SimpleCipher(簡單加密類)
public class SimpleCipher implements Cipher { /* * 以凱撒加密的方式實現加密方法 */ @Override public String encrypt(String plantTetx) { String str = ""; for (int i = 0; i < plantTetx.length(); i++) { char c = plantTetx.charAt(i); if (c >= 'a' && c <= 'z') { c += 6; if (c > 'z') c -= 26; if (c < 'a') c += 26; } if (c >= 'A' && c <= 'Z') { c += 6; if(c > 'Z') c -= 26; if(c < 'A') c += 26; } str += c; } return str; } }
-
抽象裝飾類 CipherDecorator(加密裝飾類)
public class CipherDecorator implements Cipher { private Cipher cipher; public CipherDecorator(Cipher cipher) { this.cipher = cipher; } @Override public String encrypt(String plantTetx) { // 呼叫 cipher 物件的 encrypt() 方法 return cipher.encrypt(plantTetx); } }
-
具體裝飾類 ComplexCipher(複雜加密類)
public class ComplexCipher extends CipherDecorator { public ComplexCipher(Cipher cipher) { super(cipher); } // 呼叫了父類的 encrypt() 方法 // 並通過新增的 reserve() 方法對加密後字串做進一步處理 public String encrypt(String plainText) { String result = super.encrypt(plainText); result = reverse(result); return result; } public String reverse(String text) { String str = ""; for (int i = text.length(); i > 0; i--) { str += text.substring(i - 1, i); } return str; } }
-
具體裝飾類 AdvancedCipher(高階加密類)
public class AdvancedCipher extends CipherDecorator { public AdvancedCipher(Cipher cipher) { super(cipher); } // 呼叫了父類的 encrypt() 方法 // 並通過新增的 mod() 方法對加密後字串做進一步處理 @Override public String encrypt(String plantTetx) { String result = super.encrypt(plantTetx); result = mod(result); return result; } public String mod(String text) { String str = ""; for (int i = 0; i < text.length(); i++) { String c = String.valueOf(text.charAt(i) % 6); str += c; } return str; } }
-
測試程式碼 Client
public class Client { public static void main(String[] args) { String password = "sunnyLiu"; //明文 String cpassword; //密文 Cipher sc = new SimpleCipher(); cpassword = sc.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); Cipher cc = new ComplexCipher(sc); cpassword = cc.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); //可以對裝飾之後的 cc 物件繼續進行裝飾 //從而進一步對字串進行處理,獲得更復雜的加密結果 Cipher ac = new AdvancedCipher(cc); cpassword = ac.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); } }
-
執行結果
模式優缺點
裝飾模式優點如下:
- 在擴充套件物件功能方面,裝飾者模式比繼承模式更具靈活性
- 可以通過動態的方式擴充套件物件功能,通過配置檔案在執行時選擇不同的裝飾器,從而實現不同的行為
- 可以使用多個具體裝飾類裝飾同一物件,得到功能更強大的物件
- 使用者根據需要新增新的具體構件類和具體裝飾類,原有程式碼無需改變,符合開閉原則
裝飾模式缺點如下:
- 使用裝飾者模式進行系統設計將產生很多小物件與裝飾類,增加了系統的複雜度
- 程式更加易於出錯,排查錯誤也更加困難
模式適用場景
以下情況可以考慮使用裝飾模式
- 在不影響其他物件的情況下,以透明、動態的方式給單個物件新增職責
- 當不能採用繼承對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。不能採用繼承的情況主要有兩類:第一類是系統存在大量獨立的擴充套件,為支援每一種組合將產生大量子類;第二類是因為類不能繼承(final 類)
裝飾模式的簡化
大多數情況下,裝飾模式的實現比標準的結構圖要簡單,可以對裝飾模式進行簡化。簡化過程中要注意如下幾個問題:
-
一個裝飾類的介面必須與被裝飾類介面保持相同。對於客戶端來說,無論是裝飾之前的物件還是裝飾之後的物件都可以同等對待
-
不要把太多的邏輯和狀態放在具體構件類中,可以通過裝飾類進行擴充套件
-
如果只有一個具體構件類而沒有抽象構件類,那麼抽象構件類可以作為具體構件類的子類
如果只有一個具體裝飾類,那也就沒必要再設計一個單獨的抽象裝飾類,可以把抽象裝飾類和具體裝飾類的職責合併在一個類中
透明裝飾模式和半透明裝飾模式
在透明裝飾模式中,要求客戶端完全針對抽象程式設計,裝飾模式的透明性要求客戶端程式不應該宣告具體構件型別和具體裝飾型別,而應全部宣告為抽象構件型別。如上述加密系統例項就是透明裝飾模式
Cipher sc = new SimpleCipher();
Cipher cc = new ComplexCipher(sc);
Cipher ac = new AdvancedCipher(cc);
裝飾模式的用意是在不改變介面的前提下增強原有類的功能。在增強功能時使用者往往需建立新的方法,如希望直接使用複雜加密演算法中的 reverse() 方法,這時就要採用半透明裝飾模式
SimpleCipher sc = new SimpleCipher();
ComplexCipher cc = new ComplexCipher(sc);
AdvancedCipher ac = new AdvancedCipher(cc);
Java IO 對裝飾模式的應用
這裡對 IO 流不再做過多介紹,以 InputStream 和 OutputStream 為例,它們只提供了最簡單的流處理方法,只能讀入和寫出字元,沒有緩衝處理、無法處理檔案。
InputStream 是所有位元組輸入流的父類,其中宣告的讀取以及運算元據方法會被所有位元組輸入流繼承。子類中有一個 FilterInputStream 類,它又包含了一些子類,如用於給一個輸入流新增緩衝功能的 BufferedInputStream,用於讀取原始型別的資料的 DataInputStream 等。
InputStream 的層次結構對應裝飾模式的結構,其中 InputStream 是一個抽象類,它對應裝飾模式中的抽象構件類。而 FilterInputStream、ByteArrayInputStream 等都直接繼承 InputStream 類,它們實現了在 InputStream 中定義的 read() 方法。FilterInputStream 類也是 InputStream 的子類,對應抽象裝飾類的角色。
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
// 建構函式需要傳遞一個 InputStream 物件的引用
// in 物件可以是任何繼承自 InputStream 型別的引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
...
}
BufferInputStream 是 FilterInputStream 的子類,相當於裝飾模式中的具體裝飾類,對傳入的輸入流做不同的裝飾。BufferInputStream 類提供了一個快取機制,使用一個陣列作為資料讀入的緩衝區,並覆蓋了父類的 read() 方法,在呼叫輸入流讀取資料前都會檢查快取是否已滿,實現了對輸入流物件動態新增新功能的目的,在此處的新功能即為緩衝控制。
FileInputStream inFS = new FileInputStream("temp/fileSrc.txt");
BufferedInputStream inBS = new BufferedInputStream(inFS);
//定義一個位元組陣列,用於存放緩衝資料
byte[] data = new byte[1024];
inBS.read(data);
在 Java IO 中,不僅 InputStream 用到了裝飾模式,OutputStream、Reader、Writer 等都用到了此模式。