學習Java的同學注意了!!!
學習進程中遇到甚么問題或想獲得學習資源的話,歡迎加入Java學習交換群,群號碼:183993990 我們1起學Java!
在閻宏博士的《JAVA與模式》1書中開頭是這樣描寫裝潢(Decorator)模式的:
裝潢模式又名包裝(Wrapper)模式。裝潢模式以對客戶端透明的方式擴大對象的功能,是繼承關系的1個替換方案。
裝潢模式以對客戶透明的方式動態地給1個對象附加上更多的責任。換言之,客戶端其實不會覺得對象在裝潢前和裝潢后有甚么不同。裝潢模式可以在不使用創造更多子類的情況下,將對象的功能加以擴大。
裝潢模式的類圖以下:
在裝潢模式中的角色有:
● 抽象構件(Component)角色:給出1個抽象接口,以規范準備接收附加責任的對象。
● 具體構件(ConcreteComponent)角色:定義1個將要接收附加責任的類。
● 裝潢(Decorator)角色:持有1個構件(Component)對象的實例,并定義1個與抽象構件接口1致的接口。
● 具體裝潢(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。
抽象構件角色
public interface Component { public void sampleOperation(); }
具體構件角色
public class ConcreteComponent implements Component { @Override public void sampleOperation() { // 寫相干的業務代碼 } }
裝潢角色
public class Decorator implements Component{ private Component component; public Decorator(Component component){ this.component = component; } @Override public void sampleOperation() { // 委派給構件 component.sampleOperation(); } }
具體裝潢角色
public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } @Override public void sampleOperation() { super.sampleOperation(); // 寫相干的業務代碼 } }
public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } @Override public void sampleOperation() { super.sampleOperation(); // 寫相干的業務代碼 } }
孫悟空有7102般變化,他的每種變化都給他帶來1種附加的本領。他變成魚兒時,就能夠到水里游泳;他變成鳥兒時,就能夠在天上飛行。
本例中,Component的角色便由鼎鼎大名的齊天大圣扮演;ConcreteComponent的角色屬于大圣的本尊,就是猢猻本人;Decorator的角色由大圣的7102變扮演。而ConcreteDecorator的角色便是魚兒、鳥兒等7102般變化。
抽象構件角色“齊天大圣”接口定義了1個move()方法,這是所有的具體構件類和裝潢類必須實現的。
//大圣的尊號 public interface TheGreatestSage { public void move(); }
具體構件角色“大圣本尊”猢猻類
public class Monkey implements TheGreatestSage { @Override public void move() { //代碼 System.out.println("Monkey Move"); } }
抽象裝潢角色“7102變”
public class Change implements TheGreatestSage { private TheGreatestSage sage; public Change(TheGreatestSage sage){ this.sage = sage; } @Override public void move() { // 代碼 sage.move(); } }
具體裝潢角色“魚兒”
public class Fish extends Change { public Fish(TheGreatestSage sage) { super(sage); } @Override public void move() { // 代碼 System.out.println("Fish Move"); } }
具體裝潢角色“鳥兒”
public class Bird extends Change { public Bird(TheGreatestSage sage) { super(sage); } @Override public void move() { // 代碼 System.out.println("Bird Move"); } }
客戶端類
public class Client { public static void main(String[] args) { TheGreatestSage sage = new Monkey(); // 第1種寫法 TheGreatestSage bird = new Bird(sage); TheGreatestSage fish = new Fish(bird); // 第2種寫法 //TheGreatestSage fish = new Fish(new Bird(sage)); fish.move(); } }
“大圣本尊”是ConcreteComponent類,而“鳥兒”、“魚兒”是裝潢類。要裝潢的是“大圣本尊”,也即“猢猻”實例。
上面的例子中,系統把大圣從1只猢猻裝潢成了1只鳥兒(把鳥兒的功能加到了猢猻身上),然后又把鳥兒裝潢成了1條魚兒(把魚兒的功能加到了猢猻+鳥兒身上,得到了猢猻+鳥兒+魚兒)。
如上圖所示,大圣的變化首先將鳥兒的功能附加到了猢猻身上,然后又將魚兒的功能附加到猢猻+鳥兒身上。
大多數情況下,裝潢模式的實現都要比上面給出的示意性例子要簡單。
如果只有1個ConcreteComponent類,那末可以斟酌去掉抽象的Component類(接口),把Decorator作為1個ConcreteComponent子類。以下圖所示:
如果只有1個ConcreteDecorator類,那末就沒有必要建立1個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成1個類。乃至在只有兩個ConcreteDecorator類的情況下,都可以這樣做。以下圖所示:
裝潢模式對客戶真個透明性要求程序不要聲明1個ConcreteComponent類型的變量,而應當聲明1個Component類型的變量。
用孫悟空的例子來講,必須永久把孫悟空的所有變化都當做孫悟空來對待,而如果把老孫變成的魚兒當做魚兒,而不是老孫,那就被老孫騙了,而這時候不應當產生的。下面的做法是對的:
TheGreatestSage sage = new Monkey(); TheGreatestSage bird = new Bird(sage);
而下面的做法是不對的:
Monkey sage = new Monkey(); Bird bird = new Bird(sage);
但是,純潔的裝潢模式很難找到。裝潢模式的意圖是在不改變接口的條件下,增強所斟酌的類的性能。在增強性能的時候,常常需要建立新的公然的方法。即使是在孫大圣的系統里,也需要新的方法。比如齊天大圣類并沒有飛行的能力,而鳥兒有。這就意味著鳥兒應當有1個新的fly()方法。再比如,齊天大圣類并沒有游泳的能力,而魚兒有,這就意味著在魚兒類里應當有1個新的swim()方法。
這就致使了大多數的裝潢模式的實現都是“半透明”的,而不是完全透明的。換言之,允許裝潢模式改變接口,增加新的方法。這意味著客戶端可以聲明ConcreteDecorator類型的變量,從而可以調用ConcreteDecorator類中才有的方法:
TheGreatestSage sage = new Monkey(); Bird bird = new Bird(sage); bird.fly();
半透明的裝潢模式是介于裝潢模式和適配器模式之間的。適配器模式的意圖是改變所斟酌的類的接口,也能夠通過改寫1個或幾個方法,或增加新的方法來增強或改變所斟酌的類的功能。大多數的裝潢模式實際上是半透明的裝潢模式,這樣的裝潢模式也稱做半裝潢、半適配器模式。
(1)裝潢模式與繼承關系的目的都是要擴大對象的功能,但是裝潢模式可以提供比繼承更多的靈活性。裝潢模式允許系統動態決定“貼上”1個需要的“裝潢”,或除掉1個不需要的“裝潢”。繼承關系則不同,繼承關系是靜態的,它在系統運行前就決定了。
(2)通過使用不同的具體裝潢類和這些裝潢類的排列組合,設計師可以創造出很多不同行動的組合。
由于使用裝潢模式,可以比使用繼承關系需要較少數目的類。使用較少的類,固然使設計比較易于進行。但是,在另外一方面,使用裝潢模式會產生比使用繼承關系更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
裝潢模式在Java語言中的最著名的利用莫過于Java I/O標準庫的設計了。
由于Java I/O庫需要很多性能的各種組合,如果這些性能都是用繼承的方法實現的,那末每種組合都需要1個類,這樣就會造成大量性能重復的類出現。而如果采取裝潢模式,那末類的數目就會大大減少,性能的重復也能夠減至最少。因此裝潢模式是Java I/O庫的基本模式。
Java I/O庫的對象結構圖以下,由于Java I/O的對象眾多,因此只畫出InputStream的部份。
根據上圖可以看出:
● 抽象構件(Component)角色:由InputStream扮演。這是1個抽象類,為各種子類型提供統1的接口。
● 具體構件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實現了抽象構件角色所規定的接口。
● 抽象裝潢(Decorator)角色:由FilterInputStream扮演。它實現了InputStream所規定的接口。
● 具體裝潢(ConcreteDecorator)角色:由幾個類扮演,分別是BufferedInputStream、DataInputStream和兩個不經常使用到的類LineNumberInputStream、PushbackInputStream。
裝潢模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過封裝其他對象到達設計的目的的,但是它們的形態有很大區分。
理想的裝潢模式在對被裝潢對象進行功能增強的同時,要求具體構件角色、裝潢角色的接口與抽象構件角色的接口完全1致。而適配器模式則不然,1般而言,適配器模式其實不要求對源對象的功能進行增強,但是會改變源對象的接口,以便和目標接口符合合。
裝潢模式有透明和半透明兩種,這兩種的區分就在于裝潢角色的接口與抽象構件角色的接口是不是完全1致。透明的裝潢模式也就是理想的裝潢模式,要求具體構件角色、裝潢角色的接口與抽象構件角色的接口完全1致。相反,如果裝潢角色的接口與抽象構件角色接口不1致,也就是說裝潢角色的接口比抽象構件角色的接口寬的話,裝潢角色實際上已成了1個適配器角色,這類裝潢模式也是可以接受的,稱為“半透明”的裝潢模式,以下圖所示。
在適配器模式里面,適配器類的接口通常會與目標類的接口堆疊,但常常其實不完全相同。換言之,適配器類的接口會比被裝潢的目標類接口寬。
明顯,半透明的裝潢模式實際上就是處于適配器模式與裝潢模式之間的灰色地帶。如果將裝潢模式與適配器模式合并成為1個“包裝模式”的話,那末半透明的裝潢模式倒可以成為這類合并后的“包裝模式”的代表。
InputStream類型中的裝潢模式是半透明的。為了說明這1點,無妨看1看做裝潢模式的抽象構件角色的InputStream的源代碼。這個抽象類聲明了9個方法,并給出了其中8個的實現,另外1個是抽象方法,需要由子類實現。
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的接口是完全1致的。也就是說,直到這1步,還是與裝潢模式符合合的。
public class FilterInputStream extends 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的源代碼。
public class PushbackInputStream extends FilterInputStream { 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是1個半透明的裝潢類。換言 之,它破壞了理想的裝潢模式的要求。如果客戶端持有1個類型為InputStream對象的援用in的話,那末如果in的真實類型是 PushbackInputStream的話,只要客戶端不需要使用unread()方法,那末客戶端1般沒有問題。但是如果客戶端必須使用這個方法,就 必須進行向下類型轉換。將in的類型轉換成為PushbackInputStream以后才可能調用這個方法。但是,這個類型轉換意味著客戶端必須知道它 拿到的援用是指向1個類型為PushbackInputStream的對象。這就破壞了使用裝潢模式的原始意圖。
現實世界與理論總歸是有1段差距的。純潔的裝潢模式在真實的系統中很難找到。1般所遇到的,都是這類半透明的裝潢模式。
下面是使用I/O流讀取文件內容的簡單操作示例。
public class IOTest { 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{