源碼中的設計模式--裝飾器模式

一、模式入場

有一句很經典的小品臺詞是“換個馬甲我就不認識你了嗎”,哈哈,這個比方正好用在今天要分享的裝飾器模式上。先看下《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、在裝飾者行爲中,主動調用被裝飾者行爲;

裝飾器模式很好的體現了繼承(實現)和組合的設計原則。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章