結構型:裝飾者模式及相關應用
文章目錄
裝飾者(Decorator)
在不改變原有物件的基礎之上,將功能附加到物件上。
提供了比繼承更有彈性的替代方案(擴充套件原有物件功能)。
適用場景:
- 擴充套件一個類的功能或給一個類新增附加職責。
- 動態的給一個物件新增功能,這些功能可以再動態的撤銷。
優缺點
優點:繼承的有力補充,比繼承靈活,不改變原有物件的情況下給一個物件擴充套件功能;通過使用不同裝飾類以及這些裝飾類的排列組合,可以實現不同效果;符合開閉原則。
缺點:會出現更多的程式碼,更多的類,增加程式複雜性;動態裝飾時、多層裝飾時會更復雜。
應用場景
我們考慮一個買煎餅的例子,人們可以自由地選擇是否要在煎餅上加雞蛋或者火腿,每次要加多少個,而總共價格是多少。
煎餅抽象類:
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
煎餅類:
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎餅";
}
@Override
protected int cost() {
return 8;
}
}
抽象裝飾類(並不是真正的抽象類,因為這個場景中不需要抽象方法),這個類將抽象煎餅類作為成員屬性,並且也繼承了抽象煎餅類:
public class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator (ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
加雞蛋的裝飾類,繼承了抽象裝飾類:
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一個雞蛋";
}
@Override
protected int cost() {
return super.cost()+1;
}
}
加火腿的裝飾類,繼承了抽象裝飾類:
public class SausageDecorator extends AbstractDecorator {
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一根香腸";
}
@Override
protected int cost() {
return super.cost()+2;
}
}
客戶端類:
public class Test {
public static void main(String[] args) {
ABattercake aBattercake;
aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + " 銷售價格:" + aBattercake.cost());
}
}
輸出:
煎餅 加一個雞蛋 加一個雞蛋 加一根香腸 銷售價格:12
裝飾類和具體元件類都繼承了抽象元件類。所謂裝飾,就是把這個裝飾者套在被裝飾者之上,從而動態擴充套件被裝飾者的功能,裝飾者的方法有一部分是自己的,這屬於它的功能,然後呼叫被裝飾者的方法實現,從而也保留了被裝飾者的功能。
Java I/O中的應用
在Java中應用程式通過輸入流(InputStream)的Read方法從源地址處讀取位元組,然後通過輸出流(OutputStream)的Write方法將流寫入到目的地址。
流的來源主要有三種:本地的檔案(File)、控制檯、通過socket實現的網路通訊。
下面檢視其中InputStream的類圖,而關於OutputStream、Reader、Writer等都與此類似:
由上圖可以看出只要繼承了FilterInputStream的類就是裝飾者類,可以用於包裝其他的流,裝飾者類還可以對裝飾者和類進行再包裝。以下是對其中部分類的簡要介紹:
流名稱 | 簡介 |
---|---|
ByteArrayInputStream | 位元組陣列輸入流在記憶體中建立一個位元組陣列緩衝區,從輸入流讀取的資料儲存在該位元組陣列緩衝區中 |
PipedInputStream | 訪問管道,主要線上程中使用,一個執行緒通過管道輸出流傳送資料,而另一個執行緒通過管道輸入流讀取資料,這樣可實現兩個執行緒間的通訊 |
FileInputStream | 訪問檔案,把一個檔案作為 InputStream ,實現對檔案的讀取操作 |
PushBackInputStream | 推回輸入流,可以把讀取進來的某些資料重新回退到輸入流的緩衝區之中 |
BufferedInputStream | 帶緩衝的輸入流一次讀很多位元組先放到記憶體中,等緩衝區滿的時候一次性寫入磁碟,這種方式可以減少磁碟操作次數,因此效率很高 |
DataInputStream | 允許應用程式以與機器無關方式從底層輸入流中讀取基本Java資料型別 |
Spring中的應用
檢視org.springframework.cache.transaction
下的TransactionAwareCacheDecorator
:
該類實現了Cache
介面,同時將Cache
組合到類中成為了成員屬性,所以可以大膽猜測TransactionAwareCacheDecorator
是一個裝飾類,不過這裡並沒有抽象裝飾類,且TransactionAwareCacheDecorator
沒有子類,這裡的裝飾類關係並沒有Java I/O中的裝飾關係那麼複雜。
實際上,Spring cache是對快取使用的抽象,通過它我們可以在不侵入業務程式碼的基礎上讓現有程式碼即刻支援快取。通過Spring的TransactionSynchronizationManager
將其快取操作與Spring管理的事務同步,僅在成功事務的提交之後執行實際的快取操作。
MyBatis中的應用
檢視包org.apache.ibatis.cache
:
參考資料
- 弗里曼. Head First 設計模式 [M]. 中國電力出版社, 2007.
- 慕課網java設計模式精講 Debug 方式+記憶體分析
- CS-NOTE 設計模式
- 裝飾者模式及典型應用