軟件設計模式學習(十三)裝飾模式
裝飾者模式是一種用於替代繼承的技術,它通過一種無須定義子類的方式給對象動態增加職責,使對象之間的關聯關係取代類之間的繼承關係。
模式動機
裝飾者可以在不改變一個對象本身的基礎上給對象增加額外的新行為,如一張照片,可以不改變照片本身,給她增加一個相框,使得它具有防潮功能,而且用戶可以根據需要增加不同類型的相框。在軟件開發中,類似給照片增加相框的情況隨處可見,如給一個圖形界面構件增加邊框、滾動等新特性。一般有兩種方式實現給一個類或對象增加行為:
-
繼承機制
通過繼承一個類現有類可以使子類在擁有自身方法的同時還擁有父類方法。但這種方式是靜態的,用戶不能控制增加行為的方式和時機。
-
關聯機制
將一個類的對象嵌入另一個新對象中,由另一個對象來決定是否調用嵌入對象的行為並擴展自己的行為,我們稱這個新對象(即另一個對象)為裝飾類(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 等都用到了此模式。