Java設計模式之裝飾器模式
本文由老王將建好的書房計劃請小王來幫忙,小王卻想謀權篡位,老王通過教育他引出裝飾器設計模式,第二部分針對老王提出的建設性意見實現(xiàn)裝飾器模式,第三部分針對裝飾器模式在Jdk中的IO、Spring中的緩存管理器、Mybatis的運用來加強我們的理解,第四部分說明裝飾器模式和代理模式的區(qū)別及他們各自的應用場景。
讀者可以拉取完整代碼到本地進行學習,實現(xiàn)代碼均測試通過后上傳到碼云,本地源碼下載。
一、引出問題
上篇文章(Java設計模式之組合模式)對老王的書架改造以后,老王是相當?shù)臐M意,看小王能力突出,這不老王又有了新的需求。
經(jīng)過組合模式以后老王的書被管理的井井有條,但是隨著書的增多,老王就有一些忙不過來了,老王就想讓小王幫他處理一些額外的事,比如在買書之前打掃一下書房,在晚上的時候把書房的門鎖一下;或者有人借書之前做一下記錄,借書者還書以后小王接收一下,等等。
小王聽完說這有何難,說完擼起袖子就準備改老王的代碼。老王急忙攔住了他,你真是個呆瓜,我寫的代碼你憑什么要動,你改了會不會影響我的業(yè)務邏輯,平時讓你多看書你不聽,之前學的設計模式呢?不拿出來用,眼看著讓他吃灰。
小王不好意思的撓撓頭,翻出來了他的設計模式寶典,開始尋找合適的設計模式。
小王大喊有了,之前說過的代理模式可以很好的解決這個問題,代理模式可以動態(tài)的增強對象的一些特性,我準備使用代理模式完成這個需求。
老王聽完止不住的搖搖頭,看來你是打算謀權篡位了,你是想要我整個書房的權利呀!
老王解釋說,代理模式是可以實現(xiàn)這個需求,但是在這個場景下顯然代理模式不合適,代理模式是著重對對象的控制,而我們今天的需求是在該對象的基礎之上增加他的一些功能,我們各自的業(yè)務獨立發(fā)展互不干擾。
二、裝飾器模式概念與使用
實際上,在原對象的基礎之上增加其功能就是屬于裝飾器模式。
裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬于結構型模式,它是作為現(xiàn)有的類的一個包裝。
在裝飾器模式中應該是有四個角色:
①Component抽象構件(老王抽象方法)
②ConcreteComponent 具體構件(老王實現(xiàn)方法)
③Decorator裝飾角色(裝飾者小王)
④ConcreteDecorator 具體裝飾角色(裝飾者小王實現(xiàn)方法)
在裝飾器模式中,需要增強的類(被裝飾者)要實現(xiàn)接口,裝飾者繼承被裝飾者的接口,并將被裝飾者的實例傳進去,在具體裝飾角色中調(diào)用被裝飾者的方法,在其前后定義增強的方法,在實際應用中往往裝飾角色和具體裝飾角色合二為一。
我們看下具體的代碼實現(xiàn):
抽象構件:
/** * 書的抽象構件 * @author tcy * @Date 10-08-2022 */ public abstract class ComponentBook { /** * 借書 */ public abstract void borrowBook(); /** * 買書 */ public abstract void buyBook(); }
書的具體構件:
/** * 書的具體構件 * @author tcy * @Date 10-08-2022 */ public class ConcreteComponentBook extends ComponentBook{ @Override public void borrowBook() { System.out.println("老王的書借出去..."); } @Override public void buyBook() { System.out.println("老王的書買回來..."); } }
裝飾角色:
/** * 書的裝飾者 * @author tcy * @Date 10-08-2022 */ public class DecoratorBook extends ComponentBook{ private ComponentBook componentBook; DecoratorBook(ComponentBook componentBook){ this.componentBook=componentBook; } @Override public void borrowBook() { this.componentBook.borrowBook(); } @Override public void buyBook() { this.componentBook.buyBook(); } }
書的具體裝飾角色:
/** * 子類里寫了并且使用了無參的構造方法但是它的父類(祖先)中卻至少有一個是沒有無參構造方法的 * @author tcy * @Date 10-08-2022 */ public class ConcreteDecoratorBook1 extends DecoratorBook{ ConcreteDecoratorBook1(ComponentBook componentBook) { super(componentBook); } public void cleanRoom(){ System.out.println("打掃書房..."); } public void shutRoom(){ System.out.println("關閉書房..."); } public void recordBook(){ System.out.println("記錄借出記錄..."); } public void returnBook(){ System.out.println("收到借出去的書..."); } @Override public void buyBook() { this.cleanRoom(); super.buyBook(); this.shutRoom(); System.out.println("----------------------------"); } @Override public void borrowBook() { this.recordBook(); super.borrowBook(); this.returnBook(); System.out.println("----------------------------"); } }
如果讀者的Java基礎扎實,理解裝飾器還是比較輕松的,裝飾器的實現(xiàn)方式很直觀,需要特別指出的是,在書的具體裝飾角色中,要顯示的定義一個構造方法。
基礎不太扎實的讀者可能會有一個疑問,在Java的類中默認不是會有一個無參的構造方法嗎?為什么這里還需要定義呢?
在java中一個類只要有父類,那么在它實例化的時候,一定是從頂級的父類開始創(chuàng)建。
也就是說當你用子類的無參構造函數(shù)創(chuàng)建子類對象時,會去先遞歸調(diào)用父類的無參構造方法,這時候如果某個類的父類沒有無參構造方法就會編譯出差。所以我們在子類中可以手動定義一個無參方法,或者在父類中顯示的定義一個構造方法。
客戶端:
/** * @author tcy * @Date 09-08-2022 */ public class { public static void main(String[] args) { ComponentBook componentBook=new ConcreteComponentBook(); componentBook=new ConcreteDecoratorBook1(componentBook); componentBook.borrowBook(); componentBook.buyBook(); } }
方法調(diào)用后我們可以看到執(zhí)行結果:
記錄借出記錄...
老王的書借出去...
收到借出去的書...
----------------------------
打掃書房...
老王的書買回來...
關閉書房...
----------------------------
在老王借書和買書的這個事件中,成功的織入進去小王的方法,這樣也就實現(xiàn)了裝飾器模式。
為了加強理解我們接著看裝飾器模式在我們經(jīng)常接觸的源碼中的運用。
三、應用
1、jdk中的應用IO
裝飾器在java中最典型的應用就是IO,我們知道在IO家族中有各種各樣的流,而流往往都是作用在子類之上,然后增加其附加功能,我們以InputStream 舉例。
InputStream 是字節(jié)輸入流,此抽象類是表示字節(jié)輸入流的所有類的超類。
FileInputStream是InputStream 的一個實現(xiàn)父類,BufferedInputStream是FileInputStream的實現(xiàn)父類。
實際BufferedInputStream就是裝飾者,InputStream 就是抽象構件,F(xiàn)ileInputStream是具體構件,BufferedInputStream就是對FileInputStream進行了包裝。
我們看具體的應用:
FileInputStream fileInputStream = new FileInputStream(filePath); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
直接將FileInputStream的實例傳給BufferedInputStream的構造方法,就能調(diào)用BufferedInputStream增強的一些方法了。
我們具體看BufferedInputStream裝飾器類:
public class BufferedInputStream extends FilterInputStream { private static int DEFAULT_BUFFER_SIZE = 8192; protected int marklimit; public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } ... //增強的方法 private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* grow buffer */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; } ...
BufferedInputStream類中有許多其他的方法,就是對FileInputStream類的增強。
2、Spring中的運用
Spring使用裝飾器模式有兩個典型的特征,一個是類名中含有Wrapper,另一類是含有Decorator,功能也即動態(tài)的給某些類增加一些額外的功能。
TransactionAwareCacheDecorator是處理spring有事務的時候緩存的類,我們在使用spring的cache注解實現(xiàn)緩存的時候,當出現(xiàn)事務的時候,那么緩存的同步性就需要做相應的處理了,于是就有了這個裝飾者。
public class TransactionAwareCacheDecorator implements Cache { //抽象構件 private final Cache targetCache; /** * Create a new TransactionAwareCache for the given target Cache. * @param targetCache the target Cache to decorate */ public TransactionAwareCacheDecorator(Cache targetCache) { Assert.notNull(targetCache, "Target Cache must not be null"); this.targetCache = targetCache; } /**直接調(diào)用未增強 * Return the target Cache that this Cache should delegate to. */ public Cache getTargetCache() { return this.targetCache; } //直接調(diào)用未增強 @Override public String getName() { return this.targetCache.getName(); } //直接調(diào)用未增強 @Override public Object getNativeCache() { return this.targetCache.getNativeCache(); } //直接調(diào)用未增強 @Override @Nullable public ValueWrapper get(Object key) { return this.targetCache.get(key); } //直接調(diào)用未增強 @Override public <T> T get(Object key, @Nullable Class<T> type) { return this.targetCache.get(key, type); } //直接調(diào)用未增強 @Override @Nullable public <T> T get(Object key, Callable<T> valueLoader) { return this.targetCache.get(key, valueLoader); } //先進行判斷確定是否需要增強 @Override public void put(final Object key, @Nullable final Object value) { if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { TransactionAwareCacheDecorator.this.targetCache.put(key, value); } }); } else { this.targetCache.put(key, value); } } }
Cache是抽象構件,TransactionAwareCacheDecorator就是裝飾者,而Cache的實現(xiàn)類就是具體構件。
因為并非所有的方法都會使用事務,有的普通方法就不需要裝飾,有的就需要,所以就使用了裝飾者模式來完成。
比如put()方法:
@Override public void put(final Object key, @Nullable final Object value) { if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { TransactionAwareCacheDecorator.this.targetCache.put(key, value); } }); } else { this.targetCache.put(key, value); } }
會在put前判斷是否開啟了事務TransactionSynchronizationManager.isSynchronizationActive(),如果開啟事務就調(diào)用一下額外的方法,如果沒有開始事務就調(diào)用默認的方法。
我們舉的這個例子調(diào)用的就是 TransactionSynchronizationManager.registerSynchronization()方法,也即是為當前線程注冊一個新的事務同步。
在Spring中將裝飾角色和具體裝飾角色合二為一,直接在裝飾者中實現(xiàn)要增加的方法。
3、MyBatista的運用
了解過MyBatis的大致執(zhí)行流程的讀者應該知道,Executor是MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負責SQL語句的生成和查詢緩存的維護;CachingExecutor是一個Executor的裝飾器,給一個Executor增加了緩存的功能。此時可以看做是對Executor類的一個增強,故使用裝飾器模式是合適的。
我們首先看下Executor類的繼承結構。
我們將關鍵的CachingExecutor代碼放上:
public class CachingExecutor implements Executor { //持有組件對象 private Executor delegate; private TransactionalCacheManager tcm = new TransactionalCacheManager(); //構造方法,傳入組件對象 public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { //轉發(fā)請求給組件對象,可以在轉發(fā)前后執(zhí)行一些附加動作 flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } //... }
Executor就是抽象構件,BaseExecutor是具體構件的實現(xiàn),CachingExecutor就是裝飾角色,那具體裝飾角色在哪呢?
實際中具體裝飾角色直接在裝飾角色中集成了,并沒有將具體裝飾角色完全獨立出來。
另外,Mybatis的一級緩存和二級緩存也是使用的裝飾者模式,有興趣的讀者可以拉取Mybatis的源代碼本地進行調(diào)試研究。
四、總結
到此為止,我們就將裝飾器模式的內(nèi)容講解清楚了,看到這讀者可能發(fā)現(xiàn),針對某一類需求可能會有很多設計模式都能完成需求,但一定是有最合適的那一個,就像我們今天舉的例子無論是用裝飾器模式還是代理模式都可以實現(xiàn)這個需求。
但我們看代理模式中我們列舉的例子是以租房做例子,中介將房子的權利完全移交過去,中介完全控制房子做一些改造,今天書房的需求只是讓小王來幫忙的,還是以老王為主體,小王只是做一些附加。
裝飾器模式就是在瓶里面插了一朵花,而代理模式是把瓶子都給人家了,讓人家隨便折騰。
如果我們的需求是日志收集、攔截器,代理模式是最適合的。如果是動態(tài)的增加對象的功能、限制對象的執(zhí)行條件、參數(shù)控制和檢查等使用適配器模式就更加合適了。
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。如果你想了解更多相關內(nèi)容請查看下面相關鏈接
相關文章
spring boot使用logback日志級別打印控制操作
這篇文章主要介紹了spring boot使用logback日志級別打印控制操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03java實現(xiàn)基于UDP協(xié)議網(wǎng)絡Socket編程(C/S通信)
這篇文章主要介紹了java實現(xiàn)基于UDP協(xié)議網(wǎng)絡Socket編程(C/S通信),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10SpringBoot開發(fā)項目,引入JPA找不到findOne方法的解決
這篇文章主要介紹了SpringBoot開發(fā)項目,引入JPA找不到findOne方法的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法
這篇文章主要介紹了詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09