本文的主要內容:html
裝飾者模式(Decorator Pattern):動態地給一個對象增長一些額外的職責,增長對象功能來講,裝飾模式比生成子類實現更爲靈活。裝飾模式是一種對象結構型模式。java
在裝飾者模式中,爲了讓系統具備更好的靈活性和可擴展性,咱們一般會定義一個抽象裝飾類,而將具體的裝飾類做爲它的子類spring
Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明瞭在具體構件中實現的業務方法,它的引入可使客戶端以一致的方式處理未被裝飾的對象以及裝飾以後的對象,實現客戶端的透明操做。apache
ConcreteComponent(具體構件):它是抽象構件類的子類,用於定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器能夠給它增長額外的職責(方法)。編程
Decorator(抽象裝飾類):它也是抽象構件類的子類,用於給具體構件增長職責,可是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,經過該引用能夠調用裝飾以前構件對象的方法,並經過其子類擴展該方法,以達到裝飾的目的。設計模式
ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每個具體裝飾類都定義了一些新的行爲,它能夠調用在抽象裝飾類中定義的方法,並能夠增長新的方法用以擴充對象的行爲。數組
因爲具體構件類和裝飾類都實現了相同的抽象構件接口,所以裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端並不會以爲對象在裝飾前和裝飾後有什麼不一樣。裝飾模式能夠在不須要創造更多子類的狀況下,將對象的功能加以擴展。緩存
裝飾模式的核心在於抽象裝飾類的設計。bash
煎餅抽象類微信
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
複製代碼
煎餅類,繼承了煎餅抽象類,一個煎餅 8 塊錢
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎餅";
}
@Override
protected int cost() {
return 8;
}
}
複製代碼
抽象裝飾類,須要注意的是,抽象裝飾類經過成員屬性的方式將 煎餅抽象類組合進來,同時也繼承了煎餅抽象類,且這裏定義了新的業務方法 doSomething()
public abstract class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
protected abstract void doSomething();
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
複製代碼
雞蛋裝飾器,繼承了抽象裝飾類,雞蛋裝飾器在父類的基礎上增長了一個雞蛋,同時價格加上 1 塊錢
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一個雞蛋";
}
@Override
protected int cost() {
return super.cost() + 1;
}
public void egg() {
System.out.println("增長了一個雞蛋");
}
}
複製代碼
香腸裝飾器,與雞蛋裝飾器相似,繼承了抽象裝飾類,給在父類的基礎上加上一根香腸,同時價格增長 2 塊錢
public class SausageDecorator extends AbstractDecorator{
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@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 = new Battercake();
System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());
}
}
複製代碼
輸出
煎餅, 銷售價格: 8
複製代碼
二、購買一個加雞蛋的煎餅
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());
}
}
複製代碼
輸出
煎餅 加一個雞蛋, 銷售價格: 9
複製代碼
三、購買一個加兩個雞蛋的煎餅
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());
}
}
複製代碼
輸出
煎餅 加一個雞蛋 加一個雞蛋, 銷售價格: 10
複製代碼
四、購買一個加兩個雞蛋和一根香腸的煎餅
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());
}
}
複製代碼
輸出
煎餅 加一個雞蛋 加一個雞蛋 加一根香腸, 銷售價格: 12
複製代碼
畫出UML類圖以下所示
因爲具體構件類和裝飾類都實現了相同的抽象構件接口,所以裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端並不會以爲對象在裝飾前和裝飾後有什麼不一樣。
譬如咱們給煎餅加上一個雞蛋能夠這麼寫 aBattercake = new EggDecorator(aBattercake);
,客戶端仍然能夠把 aBattercake
當成原來的 aBattercake
同樣,不過如今的 aBattercake
已經被裝飾加上了雞蛋
裝飾模式能夠在不須要創造更多子類的狀況下,將對象的功能加以擴展。
在上面的示例中,裝飾後的對象是經過抽象構建類類型 ABattercake
的變量來引用的,在雞蛋裝飾器這個類中咱們新增了 egg()
方法,若是此時咱們想要單獨調用該方法是調用不到的
除非引用變量的類型改成 EggDecorator
,這樣就能夠調用了
EggDecorator eggBattercake = new EggDecorator(aBattercake);
eggBattercake.egg();
複製代碼
在實際使用過程當中,因爲新增行爲可能須要單獨調用,所以這種形式的裝飾模式也常常出現,這種裝飾模式被稱爲半透明(Semi-transparent)裝飾模式,而標準的裝飾模式是透明(Transparent)裝飾模式。
(1) 透明裝飾模式
在透明裝飾模式中,要求客戶端徹底針對抽象編程,裝飾模式的透明性要求客戶端程序不該該將對象聲明爲具體構件類型或具體裝飾類型,而應該所有聲明爲抽象構件類型。
(2) 半透明裝飾模式
透明裝飾模式的設計難度較大,並且有時咱們須要單獨調用新增的業務方法。爲了可以調用到新增方法,咱們不得不用具體裝飾類型來定義裝飾以後的對象,而具體構件類型仍是可使用抽象構件類型來定義,這種裝飾模式即爲半透明裝飾模式。
半透明裝飾模式能夠給系統帶來更多的靈活性,設計相對簡單,使用起來也很是方便;可是其最大的缺點在於不能實現對同一個對象的屢次裝飾,並且客戶端須要有區別地對待裝飾以前的對象和裝飾以後的對象。
(1) 儘可能保持裝飾類的接口與被裝飾類的接口相同,這樣,對於客戶端而言,不管是裝飾以前的對象仍是裝飾以後的對象均可以一致對待。這也就是說,在可能的狀況下,咱們應該儘可能使用透明裝飾模式。
(2) 儘可能保持具體構件類是一個「輕」類,也就是說不要把太多的行爲放在具體構件類中,咱們能夠經過裝飾類對其進行擴展。
(3) 若是隻有一個具體構件類,那麼抽象裝飾類能夠做爲該具體構件類的直接子類。
使用 Java I/O 的時候老是有各類輸入流、輸出流、字符流、字節流、過濾流、緩衝流等等各類各樣的流,不熟悉裏邊的設計模式的話總會看得雲裏霧裏的,如今經過設計模式的角度來看 Java I/O,會好理解不少。
先用一幅圖來看看Java I/O究竟是什麼,下面的這幅圖生動的刻畫了Java I/O的做用。
由上圖可知在Java中應用程序經過輸入流(InputStream)的Read方法從源地址處讀取字節,而後經過輸出流(OutputStream)的Write方法將流寫入到目的地址。
流的來源主要有三種:本地的文件(File)、控制檯、經過socket實現的網絡通訊
下面的圖能夠看出Java中的裝飾者類和被裝飾者類以及它們之間的關係,這裏只列出了InputStream中的關係:
由上圖能夠看出只要繼承了FilterInputStream的類就是裝飾者類,能夠用於包裝其餘的流,裝飾者類還能夠對裝飾者和類進行再包裝。
這裏總結幾種經常使用流的應用場景:
流名稱 | 應用場景 |
---|---|
ByteArrayInputStream | 訪問數組,把內存中的一個緩衝區做爲 InputStream 使用,CPU從緩存區讀取數據比從存儲介質的速率快10倍以上 |
StringBufferInputStream | 把一個 String 對象做爲。InputStream。不建議使用,在轉換字符的問題上有缺陷 |
FileInputStream | 訪問文件,把一個文件做爲 InputStream ,實現對文件的讀取操做 |
PipedInputStream | 訪問管道,主要在線程中使用,一個線程經過管道輸出流發送數據,而另外一個線程經過管道輸入流讀取數據,這樣可實現兩個線程間的通信 |
SequenceInputStream | 把多個 InputStream 合併爲一個 InputStream . 「序列輸入流」類容許應用程序把幾個輸入流連續地合併起來 |
DataInputStream | 特殊流,讀各類基本類型數據,如byte、int、String的功能 |
ObjectInputStream | 對象流,讀對象的功能 |
PushBackInputStream | 推回輸入流,能夠把讀取進來的某些數據從新回退到輸入流的緩衝區之中 |
BufferedInputStream | 緩衝流,增長了緩衝功能 |
下面看一下Java中包裝流的實例:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class StreamDemo {
public static void main(String[] args) throws IOException{
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("D:\\hello.txt")));
while(in.available()!=0) {
System.out.print((char)in.readByte());
}
in.close();
}
}
複製代碼
輸出結果
hello world!
hello Java I/O!
複製代碼
上面程序中對流進行了兩次包裝,先用 BufferedInputStream將FileInputStream包裝成緩衝流也就是給FileInputStream增長緩衝功能,再DataInputStream進一步包裝方便數據處理。
若是要實現一個本身的包裝流,根據上面的類圖,須要繼承抽象裝飾類 FilterInputStream
譬如來實現這樣一個操做的裝飾者類:將輸入流中的全部小寫字母變成大寫字母
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UpperCaseInputStream extends FilterInputStream {
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase(c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; i++) {
b[i] = (byte) Character.toUpperCase((char) b[i]);
}
return result;
}
public static void main(String[] args) throws IOException {
int c;
InputStream in = new UpperCaseInputStream(new FileInputStream("D:\\hello.txt"));
try {
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
} finally {
in.close();
}
}
}
複製代碼
輸出
HELLO WORLD!
HELLO JAVA I/O!
複製代碼
整個Java IO體系都是基於字符流(InputStream/OutputStream) 和 字節流(Reader/Writer)做爲基類,下面畫出OutputStream、Reader、Writer的部分類圖,更多細節請查看其它資料
看 org.springframework.cache.transaction
包下的 TransactionAwareCacheDecorator
這個類
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public <T> T get(Object key, Class<T> type) {
return this.targetCache.get(key, type);
}
public void put(final Object key, final Object value) {
// 判斷是否開啓了事務
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 將操做註冊到 afterCommit 階段
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
} else {
this.targetCache.put(key, value);
}
}
// ...省略...
}
複製代碼
該類實現了 Cache
接口,同時將 Cache
組合到類中成爲了成員屬性 targetCache
,因此能夠大膽猜想 TransactionAwareCacheDecorator
是一個裝飾類,不過這裏並無抽象裝飾類,且 TransactionAwareCacheDecorator
沒有子類,這裏的裝飾類關係並無Java I/O 中的裝飾關係那麼複雜
該類的主要功能:經過 Spring 的 TransactionSynchronizationManager
將其 put/evict/clear
操做與 Spring 管理的事務同步,僅在成功的事務的 after-commit
階段執行實際的緩存 put/evict/clear
操做。若是沒有事務是 active
的,將當即執行 put/evict/clear
操做
注意:適配器模式的結尾也多是 Wrapper
類 ServletRequestWrapper
的代碼以下:
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;
public ServletRequestWrapper(ServletRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request cannot be null");
}
this.request = request;
}
@Override
public Object getAttribute(String name) {
return this.request.getAttribute(name);
}
//...省略...
}
複製代碼
能夠看到該類對 ServletRequest
進行了包裝,這裏是一個裝飾者模式,再看下圖,spring session 中 SessionRepositoryFilter
的一個內部類 SessionRepositoryRequestWrapper
與 ServletRequestWrapper
的關係
可見 ServletRequestWrapper
是第一層包裝,HttpServletRequestWrapper
經過繼承進行包裝,增長了 HTTP 相關的功能,SessionRepositoryRequestWrapper
又經過繼承進行包裝,增長了 Session 相關的功能
org.apache.ibatis.cache
包的文件結構以下所示
咱們經過類所在的包名便可判斷出該類的角色,Cache
爲抽象構件類,PerpetualCache
爲具體構件類,decorators
包下的類爲裝飾類,沒有抽象裝飾類
經過名稱也能夠判斷出裝飾類所要裝飾的功能
裝飾模式的主要優勢以下:
裝飾模式的主要缺點以下:
適用場景:
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
Java日誌框架:slf4j做用及其實現原理
HankingHu:由裝飾者模式來深刻理解Java I/O總體框架
HryReal:Java的io類的使用場景
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
更多內容可訪問個人我的博客:laijianfeng.org