设计模式 | 装饰者模式及典型应用
前言
本文的主要內容:
- 介紹裝飾者模式
- 示例
- 源碼分析裝飾者模式的典型應用
- Java I/O 中的裝飾者模式
- spring session 中的裝飾者模式
- Mybatis 緩存中的裝飾者模式
- 總結
裝飾者模式
裝飾者模式(Decorator Pattern):動態地給一個對象增加一些額外的職責,增加對象功能來說,裝飾模式比生成子類實現更為靈活。裝飾模式是一種對象結構型模式。
在裝飾者模式中,為了讓系統具有更好的靈活性和可擴展性,我們通常會定義一個抽象裝飾類,而將具體的裝飾類作為它的子類
角色
Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明了在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象,實現客戶端的透明操作。
ConcreteComponent(具體構件):它是抽象構件類的子類,用于定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。
Decorator(抽象裝飾類):它也是抽象構件類的子類,用于給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,并通過其子類擴展該方法,以達到裝飾的目的。
ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每一個具體裝飾類都定義了一些新的行為,它可以調用在抽象裝飾類中定義的方法,并可以增加新的方法用以擴充對象的行為。
由于具體構件類和裝飾類都實現了相同的抽象構件接口,因此裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。
裝飾模式的核心在于抽象裝飾類的設計。
示例
煎餅抽象類
public abstract class ABattercake {protected abstract String getDesc();protected abstract int cost(); }煎餅類,繼承了煎餅抽象類,一個煎餅 8 塊錢
public class Battercake extends ABattercake {@Overrideprotected String getDesc() {return "煎餅";}@Overrideprotected 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();@Overrideprotected String getDesc() {return this.aBattercake.getDesc();}@Overrideprotected int cost() {return this.aBattercake.cost();} }雞蛋裝飾器,繼承了抽象裝飾類,雞蛋裝飾器在父類的基礎上增加了一個雞蛋,同時價格加上 1 塊錢
public class EggDecorator extends AbstractDecorator {public EggDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected void doSomething() {}@Overrideprotected String getDesc() {return super.getDesc() + " 加一個雞蛋";}@Overrideprotected int cost() {return super.cost() + 1;}public void egg() {System.out.println("增加了一個雞蛋");} }香腸裝飾器,與雞蛋裝飾器類似,繼承了抽象裝飾類,給在父類的基礎上加上一根香腸,同時價格增加 2 塊錢
public class SausageDecorator extends AbstractDecorator{public SausageDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected void doSomething() {}@Overrideprotected String getDesc() {return super.getDesc() + " 加一根香腸";}@Overrideprotected int cost() {return super.cost() + 2;} }測試,購買煎餅
1、購買一個煎餅
public class Test {public static void main(String[] args) {ABattercake aBattercake = new Battercake();System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());} }輸出
煎餅, 銷售價格: 82、購買一個加雞蛋的煎餅
public class Test {public static void main(String[] args) {ABattercake aBattercake = new Battercake();aBattercake = new EggDecorator(aBattercake);System.out.println(aBattercake.getDesc() + ", 銷售價格: " + aBattercake.cost());} }輸出
煎餅 加一個雞蛋, 銷售價格: 93、購買一個加兩個雞蛋的煎餅
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());} }輸出
煎餅 加一個雞蛋 加一個雞蛋, 銷售價格: 104、購買一個加兩個雞蛋和一根香腸的煎餅
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 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);}@Overridepublic int read() throws IOException {int c = super.read();return (c == -1 ? c : Character.toUpperCase(c));}@Overridepublic 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的部分類圖,更多細節請查看其它資料
spring cache 中的裝飾者模式
看 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 操作
spring session 中的裝飾者模式
注意:適配器模式的結尾也可能是 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;}@Overridepublic Object getAttribute(String name) {return this.request.getAttribute(name);}//...省略... }可以看到該類對 ServletRequest 進行了包裝,這里是一個裝飾者模式,再看下圖,spring session 中 SessionRepositoryFilter 的一個內部類 SessionRepositoryRequestWrapper 與 ServletRequestWrapper 的關系
可見 ServletRequestWrapper 是第一層包裝,HttpServletRequestWrapper 通過繼承進行包裝,增加了 HTTP 相關的功能,SessionRepositoryRequestWrapper 又通過繼承進行包裝,增加了 Session 相關的功能
Mybatis 緩存中的裝飾者模式
org.apache.ibatis.cache 包的文件結構如下所示
我們通過類所在的包名即可判斷出該類的角色,Cache 為抽象構件類,PerpetualCache 為具體構件類,decorators 包下的類為裝飾類,沒有抽象裝飾類
通過名稱也可以判斷出裝飾類所要裝飾的功能
裝飾者模式總結
裝飾模式的主要優點如下:
裝飾模式的主要缺點如下:
適用場景:
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
HankingHu:由裝飾者模式來深入理解Java I/O整體框架
HryReal:Java的io類的使用場景
推薦閱讀
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
更多內容可訪問我的個人博客:http://laijianfeng.org
總結
以上是生活随笔為你收集整理的设计模式 | 装饰者模式及典型应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022.4.24腾讯笔试记录
- 下一篇: 【JavaWeb】1、XML、Tomca