《Java設計模式》之裝飾模式
裝飾模式(Decorator)
1. 裝飾模式(Decorator)的定義:又名包裝(Wrapper)模式。裝飾模式以對client透明的方式擴展對象的功能,是繼承關系的一個替代方案。
2. 裝飾模式以對client透明的方式動態的給一個對象附加上很多其它的責任。換言之client並不會覺的對象在裝飾前和裝飾後有什麽差別。
3. 裝飾模式能夠在不創造很多其它的子類的模式下,將對象的功能加以擴展。
4. 裝飾模式與類繼承的差別:
1) 裝飾模式是一種動態行為,對已經存在類進行任意組合,而類的繼承是一種靜態的行為。一個類定義成什麽樣的,該類的對象便具有什麽樣的功能。無法動態的改變。
2) 裝飾模式擴展的是對象的功能。不須要添加類的數量。而類繼承擴展是類的功能。在繼承的關系中,假設我們想添加一個對象的功能。我們僅僅能通過繼承關系,在子類中添加兩個方法。
3) 裝飾與繼承比較圖:
4) 裝飾模式是在不改變原類文件和使用繼承的情況下,動態的擴展一個對象的功能,它是通過創建一個包裝對象。也就是裝飾來包裹真是的對象。
5. 裝飾模式把對client的調用委派給被裝飾的類,裝飾模式的關鍵在於這樣的擴展全然透明的。
6. 裝飾模式的構成:
1) 抽象構建角色(Component):給出一個抽象的接口,以規範準備接受附加責任的對象。
相當於i/o流裏面InputStream/OutputStream
2) 詳細的構建角色(ConcreteComponent):定義一個將要接受附加責任的類。
相當於i/o裏面的FileOutputStream和FileInputStream。
3) 裝飾角色(Docorator):持有一個抽象構建(Component)角色的引用。並定義一個與抽象構件一致的接口。
相當於i/o裏面的FilerOutputStream和FilterInputStream。
4) 詳細的裝飾角色(ConcreteDecorator):負責給構建對象“貼上”附加的責任。
相當於i/o流裏面的BufferedOutputStream和BufferedInputStream
7. 裝飾模式的特點:
1) 裝飾對象和真實對象具有同樣的接口。這樣client對象就能夠以真實對象的同樣的方式和裝飾對象交互。
2) 裝飾對象包括一個真實對象的引用(reference).
3) 裝飾對象接受全部來自client的請求,它把這些請求轉發給真實的對象。
4) 裝飾對象能夠在轉發這些請求曾經或者以後添加一些附加的功能。這樣就能確保在執行時,不用改動給定對象結構就能夠在外部添加附加的功能。在面向對象的程序設計中,一般是使用繼承的關系來擴展給定類的功能。
裝飾模式的結構
裝飾模式以對客戶透明的方式動態地給一個對象附加上很多其它的責任。換言之。client並不會認為對象在裝飾前和裝飾後有什麽不同。裝飾模式能夠在不使用創造很多其它子類的情況下,將對象的功能加以擴展。
裝飾模式的類圖例如以下:
在裝飾模式中的角色有:
● 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
● 詳細構件(ConcreteComponent)角色:定義一個將要接收附加責任的類。
● 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
● 詳細裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。
源碼
抽象構件角色
- package com.bankht.Decorator;
- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:21:22
- *
- * @類說明 :抽象構件角色
- */
- public interface Component {
- public void sampleOperation();
- }
詳細構件角色
- package com.bankht.Decorator;
- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:22:05
- *
- * @類說明 :詳細構件角色
- */
- public class ConcreteComponent implements Component {
- @Override
- public void sampleOperation() {
- // TODO Auto-generated method stub
- // 寫相關的業務代碼
- }
- }
裝飾角色
package com.bankht.Decorator;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:23:03
- *
- * @類說明 :裝飾角色
- */
- public class Decorator implements Component {
- private Component component;
- public Decorator(Component component) {
- this.component = component;
- }
- @Override
- public void sampleOperation() {
- // 委派給構件
- component.sampleOperation();
- }
- }
詳細裝飾角色
package com.bankht.Decorator;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:23:34
- *
- * @類說明 :詳細裝飾角色
- */
- public class ConcreteDecoratorA extends Decorator {
- public ConcreteDecoratorA(Component component) {
- super(component);
- }
- @Override
- public void sampleOperation() {
- super.sampleOperation();
- // 寫相關的業務代碼
- }
- }
- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:23:34
- *
- * @類說明 :詳細裝飾角色
- */
- public class ConcreteDecoratorB extends Decorator {
- public ConcreteDecoratorB(Component component) {
- super(component);
- }
- @Override
- public void sampleOperation() {
- super.sampleOperation();
- // 寫相關的業務代碼
- }
- }
齊天大聖的樣例
孫悟空有七十二般變化,他的每一種變化都給他帶來一種附加的本領。他變成魚兒時。就能夠到水裏遊泳;他變成鳥兒時,就能夠在天上飛行。
本例中,Component的角色便由鼎鼎大名的齊天大聖扮演;ConcreteComponent的角色屬於大聖的本尊。就是猢猻本人;Decorator的角色由大聖的七十二變扮演。而ConcreteDecorator的角色便是魚兒、鳥兒等七十二般變化。
源碼
抽象構件角色“齊天大聖”接口定義了一個move()方法,這是全部的詳細構件類和裝飾類必須實現的。
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:28:23
- *
- * @類說明 :抽象構件角色“齊天大聖”接口定義了一個move()方法,這是全部的詳細構件類和裝飾類必須實現的。
- */
- // 大聖的尊號
- public interface TheGreatestSage {
- public void move();
- }
詳細構件角色“大聖本尊”猢猻類
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:28:50
- *
- * @類說明 :詳細構件角色“大聖本尊”猢猻類
- */
- public class Monkey implements TheGreatestSage {
- @Override
- public void move() {
- // 代碼
- System.out.println("Monkey Move");
- }
- }
抽象裝飾角色“七十二變”
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:29:24
- *
- * @類說明 :抽象裝飾角色“七十二變”
- */
- public class Change implements TheGreatestSage {
- private TheGreatestSage sage;
- public Change(TheGreatestSage sage) {
- this.sage = sage;
- }
- @Override
- public void move() {
- // 代碼
- sage.move();
- }
- }
詳細裝飾角色“魚兒”
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:29:47
- *
- * @類說明 :詳細裝飾角色“魚兒”
- */
- public class Fish extends Change {
- public Fish(TheGreatestSage sage) {
- super(sage);
- }
- @Override
- public void move() {
- // 代碼
- System.out.println("Fish Move");
- }
- }
詳細裝飾角色“鳥兒”
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:30:11
- *
- * @類說明 :詳細裝飾角色“鳥兒”
- */
- public class Bird extends Change {
- public Bird(TheGreatestSage sage) {
- super(sage);
- }
- @Override
- public void move() {
- // 代碼
- System.out.println("Bird Move");
- }
- }
client類
package com.bankht.Decorator.wukong;- /**
- * @author: 特種兵—AK47
- * @創建時間:2012-6-26 上午09:30:32
- *
- * @類說明 :client類
- */
- public class Client {
- public static void main(String[] args) {
- TheGreatestSage sage = new Monkey();
- // 第一種寫法
- TheGreatestSage bird = new Bird(sage);
- TheGreatestSage fish = new Fish(bird);
- // 另外一種寫法
- // TheGreatestSage fish = new Fish(new Bird(sage));
- fish.move();
- bird.move();
- }
- }
“大聖本尊”是ConcreteComponent類,而“鳥兒”、“魚兒”是裝飾類。
要裝飾的是“大聖本尊”,也即“猢猻”實例。
上面的樣例中,系統把大聖從一僅僅猢猻裝飾成了一僅僅鳥兒(把鳥兒的功能加到了猢猻身上),然後又把鳥兒裝飾成了一條魚兒(把魚兒的功能加到了猢猻+鳥兒身上,得到了猢猻+鳥兒+魚兒)。
如上圖所看到的,大聖的變化首先將鳥兒的功能附加到了猢猻身上。然後又將魚兒的功能附加到猢猻+鳥兒身上。
裝飾模式的簡化
大多數情況下。裝飾模式的實現都要比上面給出的示意性樣例要簡單。
假設僅僅有一個ConcreteComponent類,那麽能夠考慮去掉抽象的Component類(接口),把Decorator作為一個ConcreteComponent子類。例如以下圖所看到的:
假設僅僅有一個ConcreteDecorator類。那麽就沒有必要建立一個單獨的Decorator類,而能夠把Decorator和ConcreteDecorator的責任合並成一個類。甚至在僅僅有兩個ConcreteDecorator類的情況下,都能夠這樣做。例如以下圖所看到的:
透明性的要求
裝飾模式對client的透明性要求程序不要聲明一個ConcreteComponent類型的變量,而應當聲明一個Component類型的變量。
用孫悟空的樣例來說,必須永遠把孫悟空的全部變化都當成孫悟空來對待。而假設把老孫變成的魚兒當成魚兒,而不是老孫。那就被老孫騙了,而這時不應當發生的。
以下的做法是對的:
TheGreatestSage sage = new Monkey();- TheGreatestSage bird = new Bird(sage);
而以下的做法是不正確的:
- Bird bird = new Bird(sage);
半透明的裝飾模式
然而。純粹的裝飾模式非常難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的性能。在增強性能的時候。往往須要建立新的公開的方法。即便是在孫大聖的系統裏,也須要新的方法。比方齊天大聖類並沒有飛行的能力。而鳥兒有。這就意味著鳥兒應當有一個新的fly()方法。
再比方,齊天大聖類並沒有遊泳的能力。而魚兒有,這就意味著在魚兒類裏應當有一個新的swim()方法。
這就導致了大多數的裝飾模式的實現都是“半透明”的,而不是全然透明的。換言之,同意裝飾模式改變接口,添加新的方法。這意味著client能夠聲明ConcreteDecorator類型的變量,從而能夠調用ConcreteDecorator類中才有的方法:
TheGreatestSage sage = new Monkey();- Bird bird = new Bird(sage);
- bird.fly();
半透明的裝飾模式是介於裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口,也能夠通過改寫一個或幾個方法,或添加新的方法來增強或改變所考慮的類的功能。大多數的裝飾模式實際上是半透明的裝飾模式,這種裝飾模式也稱做半裝飾、半適配器模式。
裝飾模式的長處
(1)裝飾模式與繼承關系的目的都是要擴展對象的功能,可是裝飾模式能夠提供比繼承很多其它的靈活性。裝飾模式同意系統動態決定“貼上”一個須要的“裝飾”,或者除掉一個不須要的“裝飾”。
繼承關系則不同,繼承關系是靜態的,它在系統執行前就決定了。
(2)通過使用不同的詳細裝飾類以及這些裝飾類的排列組合。設計師能夠創造出非常多不同行為的組合。
裝飾模式的缺點
因為使用裝飾模式。能夠比使用繼承關系須要較少數目的類。
使用較少的類,當然使設計比較易於進行。可是,在還有一方面,使用裝飾模式會產生比使用繼承關系很多其它的對象。很多其它的對象會使得查錯變得困難,特別是這些對象看上去都非常相像。
設計模式在JAVA I/O庫中的應用
裝飾模式在Java語言中的最著名的應用莫過於Java I/O標準庫的設計了。
因為Java I/O庫須要非常多性能的各種組合。假設這些性能都是用繼承的方法實現的,那麽每一種組合都須要一個類。這樣就會造成大量性能反復的類出現。而假設採用裝飾模式。那麽類的數目就會大大降低。性能的反復也能夠減至最少。因此裝飾模式是Java I/O庫的基本模式。
Java I/O庫的對象結構圖例如以下。因為Java I/O的對象眾多,因此僅僅畫出InputStream的部分。
依據上圖能夠看出:
● 抽象構件(Component)角色:由InputStream扮演。這是一個抽象類,為各種子類型提供統一的接口。
● 詳細構件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實現了抽象構件角色所規定的接口。
● 抽象裝飾(Decorator)角色:由FilterInputStream扮演。
它實現了InputStream所規定的接口。
● 詳細裝飾(ConcreteDecorator)角色:由幾個類扮演,各自是BufferedInputStream、DataInputStream以及兩個不經常使用到的類LineNumberInputStream、PushbackInputStream。
半透明的裝飾模式
裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過封裝其它對象達到設計的目的的,可是它們的形態有非常大差別。
理想的裝飾模式在對被裝飾對象進行功能增強的同一時候。要求詳細構件角色、裝飾角色的接口與抽象構件角色的接口全然一致。而適配器模式則不然,一般而言,適配器模式並不要求對源對象的功能進行增強,可是會改變源對象的接口,以便和目標接口相符合。
裝飾模式有透明和半透明兩種。這兩種的差別就在於裝飾角色的接口與抽象構件角色的接口是否全然一致。透明的裝飾模式也就是理想的裝飾模式。要求詳細構件角色、裝飾角色的接口與抽象構件角色的接口全然一致。相反,假設裝飾角色的接口與抽象構件角色接口不一致,也就是說裝飾角色的接口比抽象構件角色的接口寬的話,裝飾角色實際上已經成了一個適配器角色,這樣的裝飾模式也是能夠接受的,稱為“半透明”的裝飾模式。例如以下圖所看到的。
在適配器模式裏面,適配器類的接口一般會與目標類的接口重疊,但往往並不全然同樣。
換言之。適配器類的接口會比被裝飾的目標類接口寬。
顯然,半透明的裝飾模式實際上就是處於適配器模式與裝飾模式之間的灰色地帶。假設將裝飾模式與適配器模式合並成為一個“包裝模式”的話。那麽半透明的裝飾模式倒能夠成為這樣的合並後的“包裝模式”的代表。
InputStream類型中的裝飾模式
InputStream類型中的裝飾模式是半透明的。為了說明這一點,最好還是看一看作裝飾模式的抽象構件角色的InputStream的源碼。這個抽象類聲明了九個方法,並給出了當中八個的實現,另外一個是抽象方法。須要由子類實現。
public abstract class InputStream implements Closeable {- public abstract int read() throws IOException;
- public int read(byte b[]) throws IOException {}
- public int read(byte b[], int off, int len) throws IOException {}
- public long skip(long n) throws IOException {}
- public int available() throws IOException {}
- public void close() throws IOException {}
- public synchronized void mark(int readlimit) {}
- public synchronized void reset() throws IOException {}
- public boolean markSupported() {}
- }
以下是作為裝飾模式的抽象裝飾角色FilterInputStream類的源碼。能夠看出,FilterInputStream的接口與InputStream的接口是全然一致的。也就是說,直到這一步。還是與裝飾模式相符合的。
- protected FilterInputStream(InputStream in) {}
- public int read() throws IOException {}
- public int read(byte b[]) throws IOException {}
- public int read(byte b[], int off, int len) throws IOException {}
- public long skip(long n) throws IOException {}
- public int available() throws IOException {}
- public void close() throws IOException {}
- public synchronized void mark(int readlimit) {}
- public synchronized void reset() throws IOException {}
- public boolean markSupported() {}
- }
以下是詳細裝飾角色PushbackInputStream的源碼。
- private void ensureOpen() throws IOException {}
- public PushbackInputStream(InputStream in, int size) {}
- public PushbackInputStream(InputStream in) {}
- public int read() throws IOException {}
- public int read(byte[] b, int off, int len) throws IOException {}
- public void unread(int b) throws IOException {}
- public void unread(byte[] b, int off, int len) throws IOException {}
- public void unread(byte[] b) throws IOException {}
- public int available() throws IOException {}
- public long skip(long n) throws IOException {}
- public boolean markSupported() {}
- public synchronized void mark(int readlimit) {}
- public synchronized void reset() throws IOException {}
- public synchronized void close() throws IOException {}
- }
查看源代碼,你會發現,這個裝飾類提供了額外的方法unread(),這就意味著PushbackInputStream是一個半透明的裝飾類。
換言 之。它破壞了理想的裝飾模式的要求。假設client持有一個類型為InputStream對象的引用in的話,那麽假設in的真實類型是 PushbackInputStream的話,僅僅要client不須要使用unread()方法,那麽client一般沒有問題。可是假設client必須使用這種方法,就 必須進行向下類型轉換。
將in的類型轉換成為PushbackInputStream之後才可能調用這種方法。可是,這個類型轉換意味著client必須知道它 拿到的引用是指向一個類型為PushbackInputStream的對象。這就破壞了使用裝飾模式的原始用意。
現實世界與理論總歸是有一段差距的。純粹的裝飾模式在真實的系統中非常難找到。一般所遇到的,都是這樣的半透明的裝飾模式。
以下是使用I/O流讀取文件內容的簡單操作演示樣例。
- public static void main(String[] args) throws IOException {
- // 流式讀取文件
- DataInputStream dis = null;
- try{
- dis = new DataInputStream(
- new BufferedInputStream(
- new FileInputStream("test.txt")
- )
- );
- //讀取文件內容
- byte[] bs = new byte[dis.available()];
- dis.read(bs);
- String content = new String(bs);
- System.out.println(content);
- }finally{
- dis.close();
- }
- }
- }
觀察上面的代碼。會發現最裏層是一個FileInputStream對象,然後把它傳遞給一個BufferedInputStream對象,經過BufferedInputStream處理,再把處理後的對象傳遞給了DataInputStream對象進行處理,這個過程事實上就是裝飾器的組裝過程,FileInputStream對象相當於原始的被裝飾的對象,而BufferedInputStream對象和DataInputStream對象則相當於裝飾器。
本文借鑒:
http://blog.csdn.net/m13666368773/article/details/7691871
http://www.cnblogs.com/hnrainll/archive/2012/03/23/2414180.html
http://www.cnblogs.com/java-my-life/archive/2012/04/20/2455726.html
《Java設計模式》之裝飾模式