Decorator設計模式是典型的結構型模式(在GOF的那本模式的Bible中將模式分爲:1.建立型模式;2.結構型模式;3.行爲模式三種)。它的主要用意是:動態地爲對象添加一些額外的功能。(記住上面兩種顏色的詞彙,理解裝飾器模式的精髓所在!)下面是GOF的《Element of reusable Object-Oriented Software》中對Decorator用意的概述:java
Decorator Pattern――Attaches additional responsibilities to an object dynamically . Decorators provide a flexible alternative to subclassing for extending functionality .設計模式
GOF的那本Bible中關於裝飾器模式列舉的是一個文本組件與邊框的例子(在這裏我就不舉了,主要是由於我會在書中舉一個類似的,但卻很是有說服力的例子,它對Swing中的某些原本應該使用Decorator卻沒有使用的對象的改進。同時會提出內包裝、外包裝的概念。看到這個例子後你們仔細體會吧!經過例子告訴你們一點:任何設計不是一成不變的、模式的應用是極其靈活的……)。下面我舉一個「三明治」的例子!編輯器
很 多人都吃過三明治(我除外!「沒吃過豬肉,俺可聽過豬叫」),都會知道三明治必不可少的是兩塊麪包片,而後能夠在夾層里加上蔬菜、沙拉、鹹肉等等,外面可 以塗上奶油之類的。假如如今你要爲一個三明治小店構造一個程序,其中要設計各類三明治的對象。可能你已經建立了一個簡單的Sandwich對象,如今要產生帶蔬菜的就是繼承原有的Sandwich添加一個蔬菜的成員變量,看起來很「正點」的作法,之後我還要帶鹹肉的、帶奶油的、帶蔬菜的又分爲帶青菜的、帶芹菜的、生菜的……仍是一個一個繼承是吧!假如咱們還須要即帶蔬菜又帶其它肉類,設置咱們還要求這些添加成分的任意組合,那你就慢慢繼承吧!ide
讀過幾年書的會下面這個算術,咱們有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個成分!!呵呵,怎麼辦啊!跳槽啊!跳槽了也沒人要你!!人家一看就知道你沒學設計模式)。下面咱們就使用裝飾器模式來設計這個庫吧!下圖是咱們的設計圖:工具
下面是以上各個類的意義:測試
1.Ingredient(成分):全部類的父類,包括它們共有的方法,通常爲抽象類且方法都有默認的實現,也能夠爲接口。它有Bread和Decorator兩個子類。這種實際不存在的,系統須要的抽象類僅僅表示一個概念,圖中用紅色表示。flex
2. Bread(麪包):就是咱們三明治中必須的兩片面包。它是系統中最基本的元素,也是被裝飾的元素,和IO中的媒質流(原始流)一個意義。在裝飾器模式中屬於一類角色,因此其顏色爲紫色。this
3. Decorator(裝飾器):全部其它成分的父類,這些成分能夠是豬肉、羊肉、青菜、芹菜。這也是一個實際不存在的類,僅僅表示一個概念,即具備裝飾功能的全部對象的父類。圖中用藍色表示。spa
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(); } }
以上就是一個簡單的裝飾器類!假如你對想中國式的吃法,能夠將加入饅頭、春捲皮、蛋皮……夾菜能夠爲肉絲……忽然想到了京醬肉絲。
在談及軟件中的結構,通常會用UML圖表示(UML和ANT、JUnit等都是軟件設計中基本的工具,會了沒有啊!)。下面是一個咱們常常看到的關於Decorator模式的結構圖。
1.Component就是裝飾器模式中公共方法的類,在裝飾器模式結構圖的頂層。
2.ConcreateComponent是轉換器模式中具體的被裝飾的類,IO包中的媒體流就是此種對象。
3.Decorator裝飾器模式中的核心對象,全部具體裝飾器對象的父類,完成裝飾器的部分職能。在上面的例子中Decorator類和這裏的對應。該類能夠只作一些簡單的包裹被裝飾的對象,也能夠還包含對Component中方法的實現……他有一個鮮明的特色:繼承至Component,同時包含一個Component做爲其成員變量。裝飾器模式動機中的動態地增長功能是在這裏實現的。
4.ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾器對象,他們完成具體的裝飾功能。裝飾功能的實現是經過調用被裝飾對象對應的方法,加上裝飾對象自身的方法。這是裝飾器模式動機中的添加額外功能的關鍵。
從上面圖中你可能還會發現:ConcreteDecoratorA和ConcreteDecoratorB的方法不同,這就是通常設計模式中談及裝飾器模式的「透明裝飾器」和「不透明裝飾器」。「透明裝飾器」就是整個Decorator的結構中全部的類都保持一樣的「接口」(這裏是共同方法的意思),這是一種極其理想的情況,就像餐飲的例子同樣。現實中絕大多數裝飾器都是「不透明裝飾器」,他們的「接口」在某些子類中獲得加強,主要看這個類與頂層的抽象類或者接口是否有一樣的公共方法。IO中的ByteArrayInputStream就比Inputstrem抽象類多一些方法,所以IO中的裝飾器是一個「不通明裝飾器」。下面是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中裝飾器模式的實現能夠經過下面的源代碼分析從而瞭解細節。
3 裝飾器模式與適配器模式的比較
共同點:都擁有一個目標對象。裝飾器經過包裝一個裝飾對象來擴展其功能,而又不改變其接口,這其實是基於對象的適配器模式的一種變種。
不一樣點:適配器模式須要實現另一個接口,而裝飾器模式必須實現該對象的接口。適配器模式主要是爲了接口的轉換,而裝飾者模式關注的是經過組合來動態的爲被裝飾者注入新的功能或行爲(即所謂的責任)。