## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述裝飾(Decorator)模式的:
> 裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
我們都知道,可以使用兩種方式給一個類或者對象添加行為。
一是使用繼承。繼承是給一個類添加行為的比較有效的途徑。通過使用繼承,可以使得子類在擁有自身方法的同時,還可以擁有父類的方法。但是使用繼承是靜態的,在編譯的時候就已經決定了子類的行為,我們不便于控制增加行為的方式和時機。
二是使用關聯。組合即將一個對象嵌入到另一個對象中,由另一個對象來決定是否引用該對象來擴展自己的行為。這是一種動態的方式,我們可以在應用程序中動態的控制。
與繼承相比,關聯關系的優勢就在于不會破壞類的封裝性,且具有較好的松耦合性,可以使系統更加容易維護。但是它的缺點就在于要創建比繼承更多的對象。
## 定義
裝飾者模式,動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更加有彈性的替代方案。
## 結構
裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不使用創造更多子類的情況下,將對象的功能加以擴展。
裝飾模式的類圖如下:

在裝飾模式中的角色有:
● 抽象構件(Component)角色:給出一個抽象接口,以規范準備接收附加責任的對象。
● 具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類。
● 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,并定義一個與抽象構件接口一致的接口。
● 具體裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。
**裝飾模式的簡化**
大多數情況下,裝飾模式的實現都要比上面給出的示意性例子要簡單。
如果只有一個ConcreteComponent類,那么可以考慮去掉抽象的Component類(接口),把Decorator作為一個ConcreteComponent子類。如下圖所示:

如果只有一個ConcreteDecorator類,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。甚至在只有兩個ConcreteDecorator類的情況下,都可以這樣做。如下圖所示:

**透明性的要求**
裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteComponent類型的變量,而應當聲明一個Component類型的變量。
**半透明的裝飾模式**
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的性能。在增強性能的時候,往往需要建立新的公開的方法換言之,允許裝飾模式改變接口,增加新的方法。這意味著客戶端可以聲明ConcreteDecorator類型的變量,從而可以調用ConcreteDecorator類中才有的方法。
半透明的裝飾模式是介于裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口,也可以通過改寫一個或幾個方法,或增加新的方法來增強或改變所考慮的類的功能。大多數的裝飾模式實際上是半透明的裝飾模式,這樣的裝飾模式也稱做半裝飾、半適配器模式。
## 代碼實現
**簡單例子**
抽象構件 Component Beverage.java
~~~
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
~~~
四個組件:HouseBlend.java
~~~
public class HouseBlend extends Beverage {
public HouseBlend(){
description = "HouseBlend";
}
@Override
public double cost() {
return 0.89;
}
}
~~~
DarkRoast.java
~~~
public class DarkRoast extends Beverage {
public DarkRoast(){
description = "DarkRoast";
}
@Override
public double cost() {
return 1.05;
}
}
~~~
Espresso.java
~~~
public class DarkRoast extends Beverage {
public DarkRoast(){
description = "DarkRoast";
}
@Override
public double cost() {
return 1.05;
}
}
~~~
Decat.java
~~~
public class Decat extends Beverage {
public Decat(){
description = "Decat";
}
@Override
public double cost() {
return 0.99;
}
}
~~~
CondimentDecorator.java
~~~
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
~~~
Milk.java
~~~
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " , Milk";
}
@Override
public double cost() {
return beverage.cost() + 0.3;
}
}
~~~
Mocha.java
~~~
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " , Mocha";
}
@Override
public double cost() {
return beverage.cost() + 0.20;
}
}
~~~
Soy.java
~~~
public class Soy extends CondimentDecorator{
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " , Soy";
}
@Override
public double cost() {
return beverage.cost() + 0.10;
}
}
~~~
Whip.java
~~~
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " , Whip";
}
@Override
public double cost() {
return beverage.cost() + 0.20;
}
}
~~~
測試程序
~~~
public class StarbuzzCoffee {
/**
* @param args
*/
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}
~~~
**設計模式在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對象則相當于裝飾器。
## 優點
* 1、裝飾者模式可以提供比繼承更多的靈活性
* 2、可以通過一種動態的方式來擴展一個對象的功能,在運行時選擇不同的裝飾器,從而實現不同的行為。
* 3、通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象。
* 4、具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”。
## 缺點
由于使用裝飾模式,可以比使用繼承關系需要較少數目的類。使用較少的類,當然使設計比較易于進行。但是,在另一方面,使用裝飾模式會產生比使用繼承關系更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
## 適用場景
* 1、在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
* 2、需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。 當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時。
## 總結
* 1、 組合和委托可以在運行時動態的添加新的行為,而繼承是靜態的,在系統編譯時就已經決定了對象的行為。
* 2、裝飾者模式意味著一群裝飾者類,這些類用來包裝具體組件
* 3、裝飾者可以在被裝飾者的行為前面或者后面加上自己的行為,甚至可以將被裝飾者的行為整個取代掉,從而達到特定的目的。
* 4、可以用多個裝飾者包裝一個組件。
* 5、裝飾者一般對于組件的客戶是透明的,除非客戶程序依賴于組件的具體類型。
* 6、裝飾者會導致設計中出現許多的小對象,如果過度的使用,會讓系統變得更加復雜。
* 7、裝飾者和被裝飾者對象有相同的超類型。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux