設計模式:裝飾器(Decorator)模式
一、前言
裝飾器模式也是一種非常重要的模式,在Java以及程序設計中占據着重要的地位。比如Java的數據流處理,我們可能看到數據流經過不同的類的包裝和包裹,最終形成了我們需要的流,比如說從二進制到字節流再到字符流,這中間其實就是經過了裝飾器的處理,在不改變原來的數據的基礎上,加入屬於自己的特征,就像是在一塊蛋糕上加上一些水果等裝飾品,這樣輸出的結果就不同了,我們將這種能產生類似於洋蔥一樣層層包裹的數據格式的設計模式成為裝飾器模式。
那么為什么裝飾器這樣神奇呢,幾乎可以讓我們無窮盡的包裹自身,在計算機中凡是能夠無窮盡的重復某一件事物,必然不能設定固定的數據,只能按照用戶的隨意設置來計算,那么遞歸的魅力就在此彰顯出來了,通過遞歸可以實現這一點,那么如何遞歸呢,我們想到一層套一層的數據結構(鏈表、二叉樹等),就必須在“自身”中包含“自身”;前一個“自身”可以是我們的子類,因為子類有着父類的所有特點,后一個“自身”就是具有最抽象資格的“自身”,正是因為足夠抽象使得任何繼承與這個抽象類的類都能通過里氏代換原則轉換到這個抽象類。實現了遞歸,我們只需要停止條件就可以了,停止條件就是用戶在Main中對原數據包裹的層數,肯定是有限的,因此停止是必然的。這樣我們仿似看到一個原始數據,被一層層的進行包裹,加入新的內容,最終使用最頂層的輸出方法將這個結果呈現給我們。同樣的我們還可以反着看,當遞歸開始的時候,在最頂層等待下一層的數據,然后使用頂層的方式來封裝,而下一層被啟動執行到關鍵步驟時會等待下下一層的數據返回給自身,然后是用自己的方式來封裝,就這樣一直等待下去,直到最底層的數據(本來就有)得到之后,然后一步步的返回過來,在相應的層次進行相應的封裝,最后得到了最終的數據。這就是裝飾器模式,所有的類其實最終都是同源(一致性)的,有最終的祖先,如下圖所示。
二、代碼
上圖中,StringDisplay是保存原始數據的,而Border中將父類的引用組合進入自身,形成了遞歸的必然條件,之后讓子類(SideBorder和FullBorder)來使用這個引用,從而根據自身的實際情況(要怎樣裝飾)來進行包裝,將原始的數據getRowText(rowID)進行包裹,最終通過同源祖先類的show()方法來現實,這里祖先類Display使用了面向抽象編程的模板方法,相信大家都能看出來。對比與上一個組合模式,我們可以看到上面的部分還是很相似的,但是在composite中,都實現了add()方法,通過容器來進行組織,並且使用委托機制來存儲所有的有着共同父類的元素,在顯示的時候使用了樹的深度優先遍歷。而在裝飾器模式中,我們使用的遞歸從上到下,沿着display的指向一點點的走到了底部,並且最終返回了過來。遍歷的方式有着相似之處也有着不同之處。這里要說明的是,建議大家在show()的地方打個斷點,然后跟蹤進去,一點點的看看我們的程序到底是怎么組織起來的,只有這樣我們才能理解遞歸的含義,對裝飾器有一個更深層次的認識。
Display 類:
1 package zyr.dp.decorator; 2 3 public abstract class Display { 4 5 public abstract int getColunmns(); 6 public abstract int getRows(); 7 public abstract String getTowText(int rowID); 8 public void show(){ 9 for(int i=0;i<getRows();i++){ 10 System.out.println(getTowText(i)); 11 } 12 } 13 14 }
StringDisplay類:
1 package zyr.dp.decorator; 2 3 public class StringDisplay extends Display { 4 5 String name; 6 7 public StringDisplay(String name){ 8 this.name=name; 9 } 10 11 public int getColunmns() { 12 return name.getBytes().length; 13 } 14 15 public int getRows() { 16 return 1; 17 } 18 19 public String getTowText(int rowID) { 20 if(rowID==0){ 21 return name; 22 }else{ 23 return null; 24 } 25 } 26 27 }
Border類:
1 package zyr.dp.decorator; 2 3 public abstract class Border extends Display { 4 5 protected Display display; 6 public Border(Display display){ 7 this.display=display; 8 } 9 }
SideBorder類:
1 package zyr.dp.decorator; 2 3 public class SideBorder extends Border { 4 5 String ch; 6 protected SideBorder(Display display,String ch) { 7 super(display); 8 this.ch=ch; 9 } 10 11 public int getColunmns() { 12 return display.getColunmns()+2; 13 } 14 15 public int getRows() { 16 return display.getRows(); 17 } 18 19 public String getTowText(int rowID) { 20 return ch+display.getTowText(rowID)+ch; 21 } 22 23 }
FullBorder類:
1 package zyr.dp.decorator; 2 3 public class FullBorder extends Border { 4 5 public FullBorder(Display display) { 6 super(display); 7 } 8 9 public int getColunmns() { 10 return display.getColunmns()+2; 11 } 12 13 public int getRows() { 14 return display.getRows()+2; 15 } 16 17 public String getTowText(int rowID) { 18 if(rowID==0){ 19 return "+"+makeLine("-",display.getColunmns())+"+"; 20 }else if(rowID==display.getRows()+1){ 21 return "+"+makeLine("-",display.getColunmns())+"+"; 22 }else{ 23 return "|"+display.getTowText(rowID-1)+"|"; 24 } 25 } 26 private String makeLine(String ch,int count){ 27 StringBuffer sb=new StringBuffer(); 28 for(int i=0;i<count;i++){ 29 sb.append(ch); 30 } 31 return sb.toString(); 32 } 33 34 }
Main類:
1 package zyr.dp.decorator; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 7 Display d1=new StringDisplay("朱彥榮"); 8 d1.show(); 9 System.out.println("\n"); 10 Display d2=new SideBorder(d1,"*"); 11 d2.show(); 12 System.out.println("\n"); 13 Display d3=new FullBorder(d2); 14 d3.show(); 15 System.out.println("\n"); 16 Display d4=new SideBorder( 17 new FullBorder( 18 new FullBorder( 19 new SideBorder( 20 new FullBorder( 21 new StringDisplay("李山秀") 22 ), 23 "#" 24 ) 25 ) 26 ), 27 "*" 28 ); 29 d4.show(); 30 } 31 32 }
運行結果:
我們來跟蹤一下d4.show()的執行過程:
多次跟蹤,最終到了終點,然后一步步回溯過去。跟蹤下一條語句:
。。。
。。。
。。。
就是這樣不斷地深入,得到結果之后,一步步返回,最后輸出的。
三、總結
繼承保證了父類和子類的一致性(有共同的方法),委托保證了使用委托的類和被委托對象的一致性。正如Border和Display有着一些相同的方法名稱,以及一些委托處理方法。可以看到裝飾模式中,保證了裝飾邊框與被裝飾物體的一致性(有共同父類),使用了模板方法,這個方法幾乎無處不在呀,同樣使用了委托(組合),通過在原始數據上面一層層的包裹,最終得到了我們想要的輸出,有着非常廣泛的用處。