天天看點

java裝飾器模式與java.io包

裝飾器(Decorator)模式

       Decorator設計模式是典型的結構型模式(在GOF的那本模式的Bible中将模式分為:1.建立型模式;2.結構型模式;3.行為模式三種)。它的主要用意是:動态地為對象添加一些額外的功能。(記住上面兩種顔色的詞彙,了解裝飾器模式的精髓所在!)下面是GOF的《Element of reusable Object-Oriented Software》中對Decorator用意的概述:

Decorator Pattern――Attaches additional responsibilities to an object dynamically . Decorators provide a flexible alternative to subclassing for extending functionality .

1 何時需要使用裝飾器模式

       GOF的那本Bible中關于裝飾器模式列舉的是一個文本元件與邊框的例子(在這裡我就不舉了,主要是因為我會在書中舉一個相似的,但卻非常有說服力的例子,它對Swing中的某些本來應該使用Decorator卻沒有使用的對象的改進。同時會提出内包裝、外包裝的概念。看到這個例子後大家仔細體會吧!通過例子告訴大家一點:任何設計不是一成不變的、模式的應用是極其靈活的……)。下面我舉一個“三明治”的例子!

       很多人都吃過三明治(我除外!“沒吃過豬肉,俺可聽過豬叫”),都會知道三明治必不可少的是兩塊面包片,然後可以在夾層裡加上蔬菜、沙拉、鹹肉等等,外面可以塗上奶油之類的。假如現在你要為一個三明治小店構造一個程式,其中要設計各種三明治的對象。可能你已經建立了一個簡單的Sandwich對象,現在要産生帶蔬菜的就是繼承原有的Sandwich添加一個蔬菜的成員變量,看起來很“正點”的做法,以後我還要帶鹹肉的、帶奶油的、帶蔬菜的又分為帶青菜的、帶芹菜的、生菜的……還是一個一個繼承是吧!假如我們還需要即帶蔬菜又帶其它肉類,設定我們還要求這些添加成分的任意組合,那你就慢慢繼承吧!

       讀過幾年書的會下面這個算術,我們有n種成分,在做三明治的時候任意搭配,那麼有多少種方案呢?!算算吧!你會有驚人的發現。N種成分,什麼都不要是Cn0種方案吧!要1種是Cn1吧!…..要n種是Cnn吧!加起來不就是嗎?Cn0+Cn1+……+Cnn-1+Cnn還不會啊!牛頓萊布尼茲公式記得吧!(可惜Word的公式編輯器安裝不了)總共2的n次方案。有可能前面10天寫了K個類,老闆讓你再加一種成分你就得再幹10天,下一次再加一種你可得幹20天哦!同時你可以發現你的類庫急劇地膨脹!(老闆可能會說你:XXX前K天你加了n個成分,怎麼現在這麼不上進呢?後K天隻加了1個成分啊?!!可能你會拿個比給老闆算算,老闆那麼忙會睬你嗎?!有可能你的老闆會說:不管怎麼樣我就要你加,K天你還給我加n個成分!!呵呵,怎麼辦啊!跳槽啊!跳槽了也沒人要你!!人家一看就知道你沒學設計模式)。下面我們就使用裝飾器模式來設計這個庫吧!下圖是我們的設計圖:

java裝飾器模式與java.io包

       下面是以上各個類的意義:

1.         Ingredient(成分):所有類的父類,包括它們共有的方法,一般為抽象類且方法都有預設的實作,也可以為接口。它有Bread和Decorator兩個子類。這種實際不存在的,系統需要的抽象類僅僅表示一個概念,圖中用紅色表示。

2.         Bread(面包):就是我們三明治中必須的兩片面包。它是系統中最基本的元素,也是被裝飾的元素,和IO中的媒質流(原始流)一個意義。在裝飾器模式中屬于一類角色,是以其顔色為紫色。

3.         Decorator(裝飾器):所有其它成分的父類,這些成分可以是豬肉、羊肉、青菜、芹菜。這也是一個實際不存在的類,僅僅表示一個概念,即具有裝飾功能的所有對象的父類。圖中用藍色表示。

4.         Pork(豬肉):具體的一個成分,不過它作為裝飾成分和面包搭配。

5.         Mutton(羊肉):同上。

6.         Celery(芹菜):同上。

7.         Greengrocery(青菜):同上。

總結一下裝飾器模式中的四種角色:1.被裝飾對象(Bread);2.裝飾對象(四種);3.裝飾器(Decorator);4.公共接口或抽象類(Ingredient)。其中1和2是系統或者實際存在的,3和4是實作裝飾功能需要的抽象類。

       寫段代碼體會其威力吧!(程式很簡單,但是實作的方法中可以假如如何你需要的方法,意境慢慢體會吧!)

         //Ingredient.java

public abstract class Ingredient {

         public abstract String getDescription();

         public abstract double getCost();    

         public void printDescription(){        

                   System.out.println(" Name      "+ this.getDescription());

                   System.out.println(" Price RMB "+ this.getCost());

         }

}

       所有成分的父類,抽象類有一個描述自己的方法和一個得到價格的方法,以及一個列印自身描述和價格的方法(該方法與上面兩個方法構成模闆方法哦!)

//Bread.java

public class Bread extends Ingredient {

         private String description ;

         public Bread(String desc){

                   this.description=desc ;

         }

         public String getDescription(){

                   return description ;

         }       

         public double getCost(){

                   return 2.48 ;

         }       

}

       面包類,因為它是一個具體的成分,是以實作父類的所有的抽象方法。描述可以通過構造器傳入,也可以通過set方法傳入。同樣價格也是一樣的,我就很簡單地傳回了。

//Decorator.java

public abstract class Decorator extends Ingredient {

     Ingredient ingredient ;

     public Decorator(Ingredient igd){

              this.ingredient = igd;     

     }       

     public abstract String getDescription();

     public abstract double getCost();

}

       裝飾器對象,所有具體裝飾器對象父類。它最經典的特征就是:1.必須有一個它自己的父類為自己的成員變量;2.必須繼承公共父類。這是因為裝飾器也是一種成分,隻不過是那些具體具有裝飾功能的成分的公共抽象罷了。在我們的例子中就是有一個Ingredient作為其成員變量。Decorator繼承了Ingredient類。

//Pork.java

public class Pork extends Decorator{

         public Pork(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"\n"+"Decrocated with Pork !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double porkPrice = 1.8;

                   return        basePrice + porkPrice ;

         }

}

       具體的豬肉成分,同時也是一個具體的裝飾器,是以它繼承了Decorator類。豬肉裝飾器裝飾可以所有的其他對象,是以通過構造器傳入一個Ingredient的執行個體,程式中調用了父類的構造方法,主要父類實作了這樣的邏輯關系。同樣因為方法是具體的成分,是以getDescription得到了實作,不過由于它是具有裝飾功能的成分,是以它的描述包含了被裝飾成分的描述和自身的描述。價格也是一樣的。價格放回的格式被裝飾成分與豬肉成分的種價格哦!

       從上面兩個方法中我們可以看出,豬肉裝飾器的功能得到了增強,它不僅僅有自己的描述和價格,還包含被裝飾成分的描述和價格。主要是因為被裝飾成分是它的成員變量,是以可以任意調用它們的方法,同時可以增加自己的額外的共同,這樣就增強了原來成分的功能。

//Mutton.java

public class Mutton extends Decorator{

         public Mutton(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"\n"+"Decrocated with Mutton !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double muttonPrice = 2.3;

                   return        basePrice + muttonPrice ;

         }

}

       羊肉的包裝器。

//Celery.java

public class Celery extends Decorator{

         public Celery(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"\n"+"Decrocated with Celery !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double celeryPrice =0.6;

                   return        basePrice + celeryPrice ;

         }

}

       芹菜的包裝器。

//GreenGrocery.java

public class GreenGrocery extends Decorator{

         public GreenGrocery (Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"\n"+"Decrocated with GreenGrocery  !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double greenGroceryPrice = 0.4;

                   return        basePrice + greenGroceryPrice ;

         }

}

       青菜的包裝器。

       下面我們就領略裝飾器模式的神奇了!我們有一個測試類,其中建立夾羊肉的三明治、全蔬菜的三明治、全葷的三明治。(感覺感覺吧!很香的哦!)

public class DecoratorTest{

         public static void main(String[] args){

                   Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread")));              

                   compound.printDescription();

                   compound = new Celery(new GreenGrocery(new Bread("Bread with milk")));    

                   compound.printDescription();

                   compound = new Mutton(new Pork(new Bread("Bread with cheese")));

                   compound.printDescription();

         }

}

       以上就是一個簡單的裝飾器類!假如你對想中國式的吃法,可以将加入饅頭、春卷皮、蛋皮……夾菜可以為肉絲……突然想到了京醬肉絲。

2 裝飾器模式的結構

       在談及軟體中的結構,一般會用UML圖表示(UML和ANT、JUnit等都是軟體設計中基本的工具,會了沒有啊!)。下面是一個我們經常看到的關于Decorator模式的結構圖。

java裝飾器模式與java.io包

1.      Component就是裝飾器模式中公共方法的類,在裝飾器模式結構圖的頂層。

2.      ConcreateComponent是轉換器模式中具體的被裝飾的類,IO包中的媒體流就是此種對象。

3.      Decorator裝飾器模式中的核心對象,所有具體裝飾器對象的父類,完成裝飾器的部分職能。在上面的例子中Decorator類和這裡的對應。該類可以隻做一些簡單的包裹被裝飾的對象,也可以還包含對Component中方法的實作……他有一個鮮明的特點:繼承至Component,同時包含一個Component作為其成員變量。裝飾器模式動機中的動态地增加功能是在這裡實作的。

4.      ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾器對象,他們完成具體的裝飾功能。裝飾功能的實作是通過調用被裝飾對象對應的方法,加上裝飾對象自身的方法。這是裝飾器模式動機中的添加額外功能的關鍵。

從上面圖中你可能還會發現:ConcreteDecoratorA和ConcreteDecoratorB的方法不一樣,這就是一般設計模式中談及裝飾器模式的“透明裝飾器”和“不透明裝飾器”。“透明裝飾器”就是整個Decorator的結構中所有的類都保持同樣的“接口”(這裡是共同方法的意思),這是一種極其理想的狀況,就像餐飲的例子一樣。現實中絕大多數裝飾器都是“不透明裝飾器”,他們的“接口”在某些子類中得到增強,主要看這個類與頂層的抽象類或者接口是否有同樣的公共方法。IO中的ByteArrayInputStream就比Inputstrem抽象類多一些方法,是以IO中的裝飾器是一個“不通明裝飾器”。下面是IO中輸入位元組流部分的裝飾器的結構圖。

java裝飾器模式與java.io包

1.         InputStream是裝飾器的頂層類,一個抽象類!包括一些共有的方法,如:1.讀方法――read(3個);2.關閉流的方法――close;3.mark相關的方法――mark、reset和markSupport;4.跳躍方法――skip;5.查詢是否還有元素方法――available。圖中紅色的表示。

2.         FileInputStream、PipedInputStream…五個紫色的,是具體的被裝飾對象。從他們的“接口”中可以看出他們一般都有額外的方法。

3.         FilterInputStream是裝飾器中的核心,Decorator對象,圖中藍色的部分。

4.         DataInputStream、BufferedInputStream…四個是具體的裝飾器,他們保持了和InputStream同樣的接口。

5.         ObjectInputStream是IO位元組輸入流中特殊的裝飾器,他不是FilterInputStream的子類(不知道Sun處于何種意圖不作為FileterInputStream的子類,其中流中也有不少的例子)。他和其他FilterInputStream的子類功能相似都可以裝飾其他對象。 

IO包中不僅輸入位元組流是采用裝飾器模式、輸出位元組流、輸入字元流和輸出字元流都是采用裝飾器模式。關于IO中裝飾器模式的實作可以通過下面的源代碼分析進而了解細節。

原文:http://miaoxiaodong78.blog.163.com/blog/static/18765136200701232434996/