一、模式入場
有一句很經典的小品臺詞是“換個馬甲我就不認識你了嗎”,哈哈,這個比方正好用在今天要分享的裝飾器模式上。先看下《head first 設計模式》中給的釋義。
裝飾者模式 動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比基層更有彈性的替代方案。
細心的小夥伴發現了這個釋義怎麼是裝飾者模式,今天說的不是裝飾器模式嗎,其實這兩個名稱所代表的意思是一樣的,為了保持和書上一致這裡是裝飾者模式,後續統一稱為裝飾器。
這個釋義太抽象太理論了,下面通俗的講下。說到“裝飾”二字,肯定第一時間想到的就是要有裝飾者和被裝飾者,正如前面說到的“換個馬甲我就不認識你了嗎”,這裡的馬甲可以理解為裝飾者,穿馬甲的就是被裝飾者,放到裝飾器模式裡稍微有些不同,我們繼續往下說。“裝飾”,可以簡單的理解為“偽裝”,可以偽裝成另外一個樣子,也可以偽裝成某種不同於原物的一種行為,所以在裝飾者和被裝飾者之間肯定存在某種相似,才可以使用裝飾物去裝飾被裝飾者。用在java的設計模式中,我們講的更多的是行為,也就是一個類所能完成的操作是可以用來裝飾的。
下面簡單的根據一個UML圖來了解下裝飾器模式,
上圖中Component是一個介面,有兩個方法methodA、methodB,有三個實現類ComponentA、ComponentB、ComponentDecoratorA,可以看到ComponentDecoratorA和其他兩個實現類是不一樣的,它有一個Component的屬性,其他的從UML中看不出其他,當然在methodA、methodB方法中別有洞天,後面會說到。這裡的CompoentDecoratorA其實就是一個裝飾器類,任何實現了Component介面的類,都可以被它裝飾,完成相應的功能。
可以看到裝飾器模式中有實現(繼承),還有組合。
二、深入裝飾器模式
上面對裝飾器模式已經大體有了一個瞭解,下面通過一個具體的例子來實現一個簡單的裝飾器模式。
Component.java
package com.example.decorator; public interface Component { String methodA(String params); void methodB(); }
ComponentA.java
package com.example.decorator; public class ComponentA implements Component{ //返回字串 @Override public String methodA(String params) { return "ComponentA methodA"; } //列印字串 @Override public void methodB() { System.out.println("ComponentA methodB"); } }
ComponentB.java
package com.example.decorator; public class ComponentB implements Component{ //返回字串 @Override public String methodA(String params) { return "ComponentB methodA"; } //列印字串 @Override public void methodB() { System.out.println("ComponentB methodB"); } }
ComponentDecoratorA.java
package com.example.decorator; public class ComponentDecoratorA implements Component{ //Component的例項 private Component component; //建構函式 public ComponentDecoratorA(Component component){ this.component=component; } //呼叫component的methodA方法,返回字串 @Override public String methodA(String params) { String decoratorStr=component.methodA(params); return "ComponentDecoratorA methodA,"+decoratorStr; } //呼叫component的methodB方法,列印字串 @Override public void methodB() { component.methodB(); System.out.println("ComponentDecoratorA methodB"); } }
下面看測試程式碼,
package com.example.decorator; public class TestDecorator { public static void main(String[] args) { //一個Component例項,被包裝的例項 Component component=new ComponentA(); //使用ComponentDecoratorA進行包裝 Component component1=new ComponentDecoratorA(component); String str=component1.methodA(""); System.out.println(str); } }
看下測試結果,
ComponentDecoratorA methodA,ComponentA methodA
Process finished with exit code 0
符合測試預期。
很簡單吧,這就是裝飾器模式,總結以下要點,
1、裝飾者和被裝飾者要實現統一的介面;
2、在裝飾者物件中持有被裝飾者的物件例項;
3、在裝飾者行為中,主動呼叫被裝飾者行為;
很多小夥伴會問,裝飾者和被裝飾者必須實現統一的介面(interface)嗎,使用抽象類可以嗎,其實是可以的,上述的介面可以理解為介面和抽象類,我們說只要他們有共同的行為即可,不必太拘泥於定義。
另外,在裝飾器模式中,運用了實現(繼承)和組合設計原則。
三、追尋原始碼
上面我們已經學會了使用裝飾器模式,讓我們繼續在原始碼中找尋它的影子,學習下優秀的人是怎麼使用裝飾器模式的,讓我們的程式碼越來越好。
1、java檔案系統
在Java實現的API中已經有了裝飾器模式的使用,而且在日常開發中很常用,不知道你注意到沒有,如果沒有下次在使用檔案操作類的時候可以留心下哦。
在java的檔案系統中,有位元組流和字元流,又分為輸入和輸出,分別是InputStream、OutputStream、Reader、Writer。以InputStream來舉例吧,在inputStream下有一個FilterInputStream,這是一個抽象類,該類便是一個裝飾者類的介面,裝飾所有實現了InputStream的類,
另外,這裡的InputStream是抽象類,
看下其重要的read方法,在裝飾者FilterInputStream中是怎麼實現的,
可以看到呼叫的是具體被裝飾者的read方法,由於FilterInputStream是抽象的,我們看下其具體的一個實現類也就是具體的一個裝飾者的實現,看下BufferedInputStream,
public class BufferedInputStream extends FilterInputStream { //預設的緩衝大小 private static int DEFAULT_BUFFER_SIZE = 8192; //最大 private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; //快取區 protected volatile byte buf[]; protected int count; protected int pos; protected int markpos = -1; protected int marklimit; /** * Creates a <code>BufferedInputStream</code> * and saves its argument, the input stream * <code>in</code>, for later use. An internal * buffer array is created and stored in <code>buf</code>. * * @param in the underlying input stream. */ public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); }
該類的程式碼有刪改,可以看到BufferedInputStream中定義了很多屬性,這些資料都是為了可緩衝讀取來作準備的,看到其有構造方法會傳入一個InputStream的例項。實際編碼如下
//被裝飾的物件,檔案輸入流 InputStream in=new FileInputStream("/root/doc/123.txt"); //裝飾物件,可緩衝 InputStream bufferedIn=new BufferedInputStream(in); bufferedIn.read();
上面的程式碼便使用的裝飾器模式進行的可緩衝的檔案讀取,程式碼很眼熟吧,其實你已經使用了裝飾器模式。
上面僅是拿InputStream進行了舉例說明其實,在java的IO系統中,FilterInputStream、FilterOutputStream、FilterReader、FilterWriter抽象類都是裝飾器模式的體現,其抽象類的子類都是裝飾者類。
2、mybatis快取系統
mybatis自帶一級快取,其快取設計就是使用的裝飾器模式,我們先來看下其cache介面
上圖紅框中標出的是Cache介面的直接實現PerpetualCache,這個類可以作為被裝飾者,再看其他的實現均在org.apache.ibatis.cache.decorators包中,那麼也就是裝飾者,看下LruCache的實現,僅貼出部分程式碼,
public class LruCache implements Cache { //Cache例項 private final Cache delegate; //實現LRU演算法的輔助map private Map<Object, Object> keyMap; private Object eldestKey; //建構函式,傳入一個Cache,用來初始胡delegate和其他引數 public LruCache(Cache delegate) { this.delegate = delegate; this.setSize(1024); } }
這個程式碼和最開始演示的Component的那個例子很像,至於LRU快取怎麼實現的,各位小夥伴可以自行了解。下次再使用到mybatis的快取,你就可以自豪的說這是裝飾器模式。
3、mybatis的Executor執行器
在mybatis中真正負責執行sql語句的是Executor介面,
該介面有以下幾個實現類:CachingExecutor、BaseExecutor、SimpleExecutor等,重點看下CachingExecutor、SimpleExecutor
CachingExecutor應該是裝飾者,看下SimpleExecutor
這個應該是被裝飾者,它在執行具體的操作。
四、總結
本文分享了裝飾器模式及在原始碼中的使用,需要幾種以下幾點,
1、裝飾者和被裝飾者要實現統一的介面;
2、在裝飾者物件中持有被裝飾者的物件例項;
3、在裝飾者行為中,主動呼叫被裝飾者行為;
裝飾器模式很好的體現了繼承(實現)和組合的設計原則。