裝飾模式又名包裝(Wrapper)模式。裝飾模式以對用戶端透明的方式擴充對象的功能,是繼承關系的一個替代方案。
裝飾模式的結構
裝飾模式以對客戶透明的方式動态地給一個對象附加上一些責任。換言之,用戶端并不會覺得對象在裝飾前和裝飾後有什麼不同。裝飾模式可以在不使用創造更多子類的情況下,将對象的功能加以擴充。
裝飾模式的類圖如下:
在裝飾模式中的角色有:
● 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
● 具體構件(ConcreteComponent)角色:定義一個将要接收附加責任的類。
● 裝飾(Decorator)角色:持有一個構件(Component)對象的執行個體,并定義一個與抽象構件接口一緻的接口。
● 具體裝飾(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();
// 寫相關的業務代碼
}
}
裝飾模式的簡化
大多數情況下,裝飾模式的實作都要比上面給出的示意性例子要簡單。
如果隻有一個ConcreteComponent類,那麼可以考慮去掉抽象的Component類(接口),把Decorator作為一個ConcreteComponent子類。如下圖所示:
如果隻有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。甚至在隻有兩個ConcreteDecorator類的情況下,都可以這樣做。如下圖所示:
透明性的要求
裝飾模式對用戶端的透明性要求程式不要聲明一個ConcreteComponent類型的變量,而應當聲明一個Component類型的變量。
半透明的裝飾模式
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的功能。在增強功能的時候,往往需要建立新的公開方法。
這就導緻了大多數的裝飾模式的實作都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。這意味着用戶端可以聲明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的接口是完全一緻的。也就是說,直到這一步,還是與裝飾模式相符合的。
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是一個半透明的裝飾類。換言 之,它破壞了理想的裝飾模式的要求。如果用戶端持有一個類型為InputStream對象的引用in的話,那麼如果in的真實類型是 PushbackInputStream的話,隻要用戶端不需要使用unread()方法,那麼用戶端一般沒有問題。但是如果用戶端必須使用這個方法,就 必須進行向下類型轉換。将in的類型轉換成為PushbackInputStream之後才可能調用這個方法。但是,這個類型轉換意味着用戶端必須知道它 拿到的引用是指向一個類型為PushbackInputStream的對象。這就破壞了使用裝飾模式的原始用意。
現實世界與理論總歸是有一段差距的。純粹的裝飾模式在真實的系統中很難找到。一般所遇到的,都是這種半透明的裝飾模式。
下面是使用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{
dis.close();
}
}
}
觀察上面的代碼,會發現最裡層是一個FileInputStream對象,然後把它傳遞給一個BufferedInputStream對象,經過BufferedInputStream處理,再把處理後的對象傳遞給了DataInputStream對象進行處理,這個過程其實就是裝飾器的組裝過程,FileInputStream對象相當于原始的被裝飾的對象,而BufferedInputStream對象和DataInputStream對象則相當于裝飾器。
Android 裝飾器模式的執行個體:
Context類中也存在裝飾者模式的運用,大家看源碼中
-
是抽象類就是裝飾者模式的Component:元件對象接口。abstract class Context
- 而真正的實作是在ContextImpl中完成,它繼承自Context抽象類并實作抽象方法。是ConcreteComponent:具體的元件對象,實作元件對象接口,就是被裝飾的原始對象。
- 而Decorator所有裝飾器的抽象父類是ContextWrapper。從代碼上看是完全和裝飾者模式一樣的實作。内部持有一個Component對象,就是持有一個被裝飾的對象。
1 public class ContextWrapper extends Context {
2 Context mBase;
3
4 public ContextWrapper(Context base) {
5 mBase = base;
6 }
7
8 //省略代碼 隻看這個方法
9 @Override
10 public void startActivity(Intent intent) {
11 mBase.startActivity(intent);
12 }
- 最後誰是具體的裝飾器對象呢?其實就是我們的Activity、Service和Application這些能夠啟動Activity的實作類啊。隻是我們一般沒有重寫startActivity()方法由系統調用了我們沒有注意到。
借用一張UML圖可以很好的說明它們之前的關系: