《一天一模式》— 裝飾者模式

1、裝飾者模式的概念

裝飾者模式可以動態地將功能附加到對象上,若要擴展功能,裝飾者提供了比繼承更有彈性的代替方案。java

聽懂了這句話就不用往下看了,說明你會了。算法

聽不懂我以爲也正常,若是用一句話能學會就沒人看書了。像我這種笨人,都是學會了一個模式,而後往它的定義上套。安全

2、何時使用裝飾者模式

在說明何時使用裝飾者模式以前,先舉一個咱們平時都在使用的例子。框架

Java的I/O框架中就大量的使用了裝飾者模式,例如《Head First》中所說的:函數

如上圖,裝飾者從表面上看就是有一個原始的被裝飾者對象,而後可能通過多層的裝飾附加不少新的功能,還能不修改被裝飾者。測試

雖然繼承關係也能達到這種預期,可是Java中的繼承是很寶貴的,由於一個類只能繼承一個父類。有可能父類已經用於模版方法或者其餘用途了。this

裝飾者能夠簡介的避免繼承濫用的問題。因此使用了裝飾者模式,可以在不修改任何底層代碼的狀況下,給你的(或別人的)對象賦予新的功能。加密

總結一下何時須要使用裝飾者:spa

  • 非繼承下,在不改變被裝飾者(底層對象)的狀況下添加新功能;
  • 能夠動態、靈活的添加新功能;
  • 能夠防止類爆炸,在業務功能特別多的狀況下;

以上是可使用裝飾者模式的場景,能夠還不是很清晰,下面舉個例子來看看如何使用裝飾界解決問題,並舉一個不使用裝飾者時的反面教材。設計

3、怎麼使用裝飾者模式

以一個簡單的需求來實現裝飾者模式,要求作一個通用的API解密組件,實現2種功能:

  • 第一個需求,能夠經過不一樣解密算法(RSA,AES等)來解密數據體;
  • 第二個需求,解密後對參數進行簽名驗證,驗證的方式能夠是SHA或者MD5等;

需求很常見,大多API調用作安全策略時,給咱們的數據都是加密的,咱們須要經過解密算法拿到數據的明文,而後大多數作法是將參數根據名稱排序,放到一個字符串中,經過算法進行加密,而後與數據中的一個sign屬性的值進行對比,若是一直表明驗證簽名經過,就能夠證實本次請求是安全的。

這裏主要將加密和驗證簽名兩個動做拿出來看成需求,具體實現不進行實現。

3.1 普通設計的弊端

設計一個多級的繼承類,RSADecode和AESDecode這一層實現了普通的RSA或AES解密,下一層實現了幾種解密和驗籤方法。

這種設計有個問題,那就是容易類爆炸,每當多一種新的實現方式,都要成倍的增長類的數量,,例如加一個Base64的Decode,須要加3個類(Base64Decode類,Base64AndSHADecode類,Base64AndMD5Decode類),實現越多,須要加的類就越多,這和橋接模式中的類爆炸緣由是同樣的。

3.2 設計升級後帶來的弊端

能夠把須要的算法,以布爾值的方式聲明到父類中,而後在子類經過if,else進行實現。這樣就不會有類爆炸的弊端。先來看代碼:

public abstract class Decode {

    boolean isRSA;
    boolean isAES;

    abstract void decode();

}

作一個父類,其中聲明解密須要的算法的布爾值,以及一個抽象方法。

public class DecodeImpl extends Decode {

    boolean isMD5;
    boolean isSHA;

    void decode() {
        if (isRSA) {
            // 進行RSA操做
        } else if (isAES) {
            // 進行AES操做
        }
    }

}

解密的對象,實現抽象方法,而後判斷布爾值來執行具體的解密操做。隨便定義好驗證簽名須要的布爾值。

public class Sign extends DecodeImpl {

    void decode() {
        super.decode();
        if (isAES) {
            // 進行RSA操做
        } else if (isMD5) {
            // 進行AES操做
        }
    }

}

最後定義驗證簽名的對象,作法根解密對象是同樣的,先調用解密方法,而後根據布爾值執行驗證簽名的業務。

這麼作會有幾個問題:

  • 一旦出現新的加密方式,好比在decode里加一個isBase64,就要添加decodeimpl中的if,else代碼塊,這就違反了開閉原則,咱們要去修改以前運行着的代碼,而後以前用到過這段代碼的地方,都須要讓測試去覆蓋到;
  • 有可能須要進行2次解密的場景,好比要進行兩次RSA才能解密,一樣要修改decodeimpl中已經寫好的代碼;
  • 目前的業務方式太固定,先解密在驗簽證,沒法適應業務流程發生變化,假如流程發生變化,在解密和驗證簽名中間加入其它的業務邏輯,那麼總體的設計結構就要發生變化,影響範圍太大了;

3.3 使用裝飾者模式來解決弊端

如圖所示,這就是裝飾者模式的實現類圖:

  • 首先定義一個Component的父類,讓被裝飾者(decode對象們)與裝飾者(sign對象們)都繼承這個父類;
  • 而後在裝飾者對象種採用依賴的方式,引用到被裝飾者,而後在本身的方法中進行業務實現;

具體的代碼以下:

/**
 * 裝飾者模式父級組件。
 */
public abstract class Component {

    public abstract void decode(final String data);

}

// -----------------------------------------------------------------
// 這裏將幾種解密的方法做爲被裝飾者,被裝飾者繼承了Component對象
// -----------------------------------------------------------------

/**
 * 被裝飾者。
 */
public class RSADecode extends Component {

    public void decode(final String data) {
        System.out.println("RSA解密:" + data);
    }

}

/**
 * 被裝飾者。
 */
public class AESDecode extends Component {

    public void decode(final String data) {
        System.out.println("AES解密:" + data);
    }

}

// -----------------------------------------------------------------
// 這裏將驗證簽名做爲裝飾者,也繼承了Component對象。
// 另外,裝飾者父類中,還定義了被裝飾者,須要經過構造函數將它傳遞進來。
// -----------------------------------------------------------------


/**
 * 裝飾者抽象類。
 */
public abstract class ValidateSign extends Component {

    private Component component;

    /**
     * 在構造函數中傳入被裝飾者。
     */
    public ValidateSign(Component component) {
        this.component = component;
    }

    public Component getComponent() {
        return this.component;
    }

}

/**
 * 裝飾者。
 */
public class SHASign extends ValidateSign {

    public SHASign(Component component) {
        super(component);
    }

    public void decode(String data) {
        // 先調用被裝飾者
        super.getComponent().decode(data);
        // 在實現裝飾者的功能
        System.out.println("SHA驗籤:" + data);
    }

}

/**
 * 裝飾者。
 */
public class MD5Sign extends ValidateSign {

    public MD5Sign(Component component) {
        super(component);
    }

    public void decode(String data) {
        // 先調用被裝飾者
        super.getComponent().decode(data);
        // 在實現裝飾者的功能
        System.out.println("MD5驗籤:"  + data);
    }

}

使用裝飾者模式進行業務調用:

public class Client {

    public static void main(String[] args) {
        // 普通的RSA解密
        System.out.println("普通調用,不使用裝飾者模式。");
        Component component = new RSADecode();
        component.decode("Hello World.");
        System.out.println("");

        // 裝飾者模式應用,用SHASign來裝飾RSADecode
        // 爲組件加入了SHA驗籤的能力
        System.out.println("裝飾者模式調用。");
        Component component1 = new SHASign(new RSADecode());
        component1.decode("Hello World.");
        System.out.println("");

        // 解密後,還能夠進行兩次驗籤,很靈活
        System.out.println("裝飾者模式調用。");
        Component component2 = new MD5Sign(new SHASign(new RSADecode()));
        component2.decode("Hello World.");
    }

}

// 輸出:
普通調用,不使用裝飾者模式。
RSA解密:Hello World.

裝飾者模式調用。
RSA解密:Hello World.
SHA驗籤:Hello World.

裝飾者模式調用。
RSA解密:Hello World.
SHA驗籤:Hello World.
MD5驗籤:Hello World.

4、總結

回顧概念:

裝飾者模式可以動態地將功能附加到對象上,若要擴展功能,裝飾者提供了比繼承更有彈性的代替方案。

動態添加功能:動態將功能附加到對象上,並且能夠隨意組合,在Main函數中咱們也作到了,能夠屢次對被裝飾者進行裝飾,裝飾的順序也能夠隨意調整。

比繼承有彈性:裝飾者中使用了關聯關係的方式,將被裝飾者經過構造函數傳入,創建關聯。

弊端:但它也有一些弊端,會出現不少小類。具體的使用須要根據業務場景進行權衡。

以上就是裝飾者模式的一些理解, 有不足之處請你們矯正,謝謝。

相關文章
相關標籤/搜索