《一天一模式》— 装饰者模式

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函数中咱们也作到了,能够屡次对被装饰者进行装饰,装饰的顺序也能够随意调整。

比继承有弹性:装饰者中使用了关联关系的方式,将被装饰者经过构造函数传入,创建关联。

弊端:但它也有一些弊端,会出现不少小类。具体的使用须要根据业务场景进行权衡。

以上就是装饰者模式的一些理解, 有不足之处请你们矫正,谢谢。

相关文章
相关标签/搜索