前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >装饰者模式与IO流的应用

装饰者模式与IO流的应用

作者头像
java技术爱好者
发布2020-09-22 16:22:00
5110
发布2020-09-22 16:22:00
举报
文章被收录于专栏:java技术爱好者java技术爱好者

定义

装饰者模式是一种对象结构型模式。动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。

通俗解释

上面的定义在网上是随处可见的描述,怎么解释呢。比如:我前几天和女朋友去买戒指,珠宝店的销售给我推荐了一种自由搭配的原创戒指。他跟我介绍戒指的元素需要选择材质(黄金,铂金,彩金)、表面工艺(拉丝,磨砂,光滑,铸造)、镶钻(内嵌,外嵌)、指环大小等等,然后组成一个戒指。这种就是装饰者模式的应用,原型是一个戒指,不断地给对象添加额外的职责,然后得到最终想要的产品。这样就可以通过不同的搭配产生很多不同类型的戒指。

后面那句装饰者模式比生成子类更为灵活怎么理解。如果用子类去描述的话,要把每一种搭配的结果都变成一个子类,也就是要穷举,就会产生很多子类,也就是造成“类爆炸”。所以就会说装饰者模式更加灵活。

来个例子

现在有一个需求,要求做一个加密的工具类,对传入的字符串加密。加密的算法有很多,有MD5、AES、DES等等,一般加密都不是单独使用一种加密算法,而是多种混合一起使用,这样可以提高安全性。

现在有三种算法:MD5、AES、DES。做一个工具类,给系统提供加密的服务,要求可以自由搭配使用。

使用继承的方式实现

我们就创建一个抽象类EncryptionBase,每一种组合方式就创建一个子类继承EncryptionBase,现在有三种加密方式,很容易我们可以穷举完,总共有6种组合。请看以下代码:

首先创建一个抽象类EncryptionBase

代码语言:javascript
复制
public abstract class EncryptionBase {
    public abstract String encrypt(String string,String password);
}

接着创建子类继承抽象类,并且实现其方法。以其中一个为例,其他实现类都类似:

代码语言:javascript
复制
public class AESandDESandMD5Encryption extends EncryptionBase {
    @Override
    public String encrypt(String string, String password) {
        //网上可以找具体加密的代码,我这里篇幅受限就不展示了
        //AES加密
        byte[] encryptByAES = AESUtil.encrypt(string, password);
        //DES加密
        byte[] encryptByDES = DESUtil.encrypt(encryptByAES, password);
        //MD5加密
        return MD5Util.encryptByMD5(new String(encryptByDES) + password);
    }
}

我们就可以实现以下效果,有6个实现类分别实现了3种加密算法的不同顺序。

代码语言:javascript
复制
public static void main(String[] args) {
        String string = "需要加密的字符串";
        //秘钥
        String password = "12345678";

        //第一种加密顺序:AES->DES->MD5
        EncryptionBase AESandDESandMD5 = new AESandDESandMD5Encryption();
        //第二种加密顺序:AES->MD5->DES
        EncryptionBase AESandMD5andDES = new AESandMD5andDESEncryption();
        //第三种加密顺序:DES->AES->MD5
        EncryptionBase DESandAESandMD5 = new DESandAESandMD5Encryption();
        //第四种加密顺序:DES->MD5->AES
        EncryptionBase DESandMD5andAES = new DESandMD5andAESEncryption();
        //第五种加密顺序:MD5->DES->AES
        EncryptionBase MD5andDESandAES = new MD5andDESandAESEncryption();
        //第六种加密顺序:MD5->AES->DES
        EncryptionBase MD5andAESandDES = new MD5andAESandDESEncryption();
    }

以上就是使用继承的方式来完成这个需求。看起来没什么问题,但是仔细思考你会发现几个问题。

  1. 会创建很多子类。为什么3种算法是6个类呢?这是根据数学的排列组合3*2*1=6,假设再多两种算法呢?那就是5*4*3*2*1=120,那就是120个类了!这就是“类爆炸”
  2. 不符合开闭原则。假设增加了新的算法,那就要修改原来的类,不利于代码的维护。
  3. 假如其中一种加密算法要用两次,比如双重MD5加密,那也是很难扩展的。

如果你不会装饰者模式,那估计要加班加点去写代码,创建很多类。如果你会装饰者模式,那问题就很简单了,那怎么做呢?请继续看下去。

使用装饰者模式实现

首先创建三个继承EncryptionBase的加密算法基础类,分别实现三种加密算法。

MD5加密

代码语言:javascript
复制
public class MD5Encryption extends EncryptionBase {
    @Override
    public String encrypt(String string, String password) {
        System.out.println("使用MD5加密,得到基础密文");
        return MD5Util.encryptByMD5(string + password);
    }
}

AES加密

代码语言:javascript
复制
public class AESEncryption extends EncryptionBase {
    @Override
    public String encrypt(String string, String password) {
        System.out.println("使用AES加密,得到基础密文");
        return new String(AESUtil.encrypt(string, password));
    }
}

DES加密

代码语言:javascript
复制
public class DESEncryption extends EncryptionBase {
    @Override
    public String encrypt(String string, String password) {
        System.out.println("使用DES加密,得到基础密文");
        return new String(DESUtil.encrypt(string.getBytes(), password));
    }
}

接着创建装饰抽象类EncryptionDecorator,继承EncryptionBase

代码语言:javascript
复制
public abstract class EncryptionDecorator extends EncryptionBase {

    //定义一个父类的成员变量,用来存储其他装饰类,或者基础加密类
    private EncryptionBase encryption;

    public EncryptionDecorator(EncryptionBase encryption) {
        this.encryption = encryption;
    }

    @Override
    public String encrypt(String string, String password) throws Exception{
        return encryption.encrypt(string, password);
    }
}

然后实现三种加密的装饰者实现类,继承抽象装饰者类EncryptionDecorator

MD5加密装饰者实现类

MD5EncryptionDecorator

代码语言:javascript
复制
public class MD5EncryptionDecorator extends EncryptionDecorator {

    public MD5EncryptionDecorator(EncryptionBase encryption) {
        //有参构造器获取到参数,调用父类的有参构造器,
        //当下面encrypt()方法里调用父类的加密算法就会调用传入的算法实现类的加密算法
        super(encryption);
    }

    @Override
    public String encrypt(String string, String password) throws Exception{
        //首先调用父类的加密方法,得到父类的算法加密后的结果
        String encrypt = super.encrypt(string, password);
        System.out.println("使用MD5加密");
        //得到的密文,再用MD5算法加密,返回
        return MD5Util.encryptByMD5(encrypt + password);
    }
}

AES加密装饰者实现类

AESEncryptionDecorator

代码语言:javascript
复制
public class AESEncryptionDecorator extends EncryptionDecorator {

    public AESEncryptionDecorator(EncryptionBase encryption) {
        super(encryption);
    }

    @Override
    public String encrypt(String string, String password) throws Exception{
        //首先调用父类的加密方法,得到父类的算法加密后的结果
        String encrypt = super.encrypt(string, password);
        System.out.println("使用AES加密");
        //得到的密文,再用AES算法加密,返回
        return new String(AESUtil.encrypt(encrypt, password),"UTF-8");
    }
}

DES加密装饰者实现类

DESEncryptionDecorator

代码语言:javascript
复制
public class DESEncryptionDecorator extends EncryptionDecorator {
    public DESEncryptionDecorator(EncryptionBase encryption) {
        super(encryption);
    }

    @Override
    public String encrypt(String string, String password) throws Exception{
        //首先调用父类的加密方法,得到父类的算法加密后的结果
        String encrypt = super.encrypt(string, password);
        System.out.println("使用DES加密");
        //得到的密文,再用DES算法加密,返回
        return new String(DESUtil.encrypt(encrypt.getBytes(), password),"UTF-8");
    }
}

大功告成!我们用main()方法测试一下:

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) throws Exception{
        String string = "需要加密的字符串";
        String password = "12345678";
        //第一种加密顺序:AES->DES->MD5
        EncryptionBase encryptionBase = new MD5EncryptionDecorator(new DESEncryptionDecorator(new AESEncryption()));
        encryptionBase.encrypt(string, password);
    }
}

控制台打印结果:

代码语言:javascript
复制
/**
使用AES加密,得到基础密文
使用DES加密
使用MD5加密
*/

我们可以看到结果是很完美地实现了,你可以任意搭配加密算法,即使加多N种算法,我们也不会呈指数增加类的数量,只需要增加M*N个类即可,M是基础构件数量,N是具体装饰类数量。

原理是什么呢?我们不能说只学到形式,而不明白原理。接下来看类图。

在IDEA可以选中类名,然后右键,选中“Diagrams”,再选中“show Diagrams…”,就可以打开类图。

代码语言:javascript
复制
//MD5(DES(AES)),最顶层的父类是AES,所以先执行,第二层是DES,第二执行,最外层是MD5第三执行
EncryptionBase encryptionBase = new MD5EncryptionDecorator(new DESEncryptionDecorator(new AESEncryption()));
encryptionBase.encrypt(string, password);

以上面这句代码为例,那么调用顺序就是:AES->DES->MD5

这就是装饰者模式的原理,其实很简单的,很容易就可以看清楚。

装饰者模式与I/O流

看了上面的代码,很容易我们能联想到IO流也有类似的创建方式,比如我们要用文件缓冲输入流,那就要这样创建:

代码语言:javascript
复制
InputStream inputStream 
    = new BufferedInputStream(new FileInputStream(new File("/D:abc.text")));

可以看出IO流使用了装饰者模式。

查看BufferedInputStream源码,如下:

代码语言:javascript
复制
public class BufferedInputStream extends FilterInputStream {
    //有参构造器
    public BufferedInputStream(InputStream in, int size) {
        //调用父类构造器,这是关键
        //通过上面我们学过的例子,可以知道BufferedInputStream是装饰实现类
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

关键在FilterInputStream这个类,这是装饰者模式的基类。查看源码:

代码语言:javascript
复制
public class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
}

FilterInputStream类似于加密算法例子的EncryptionDecorator类。我们可以通过加密算法的例子和这个作对比,就可以很容易地看出他们的关系。类图如下:

FileInputStream就是基础构件类,可以通过FilterInputStream的子类去做扩展,增加额外的功能,比如可以使用BufferedInputStream增加缓冲的作用。

接着我们真正理解了IO流的装饰者模式的应用后,我们可以写一个扩展类,实现一个功能:读取磁盘的文件,把所有字母变成大写的字母。代码如下:

代码语言:javascript
复制
public class CapitalizaInputStream extends FilterInputStream {

    public CapitalizaInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for (int i = off; i < off + result; i++){
            //如果是小写字母,转成大写,其他不是小写字母的不变
            if(Character.isLetter((char)b[i])){
                b[i] = (byte) Character.toUpperCase((char) b[i]);
            }
        }
        return result;
    }
}

abc.txt文件内容:

代码语言:javascript
复制
abcdefghijklmnopqrstuvwxyz

Main方法测试代码:

代码语言:javascript
复制
public static void main(String[] args) throws Exception {
        InputStream inputStream 
            = new CapitalizaInputStream(new FileInputStream(new File("D://abc.txt")));
        byte[] bytes = new byte[1024 * 2];
        int c;
        while ((c = inputStream.read(bytes, 0, bytes.length)) != -1) {
            System.out.println(new String(bytes, 0, c));
        }
        inputStream.close();
    }

控制台打印结果:

代码语言:javascript
复制
ABCDEFGHIJKLMNOPQRSTUVWXYZ

以上就是IO流关于装饰者模式的扩展,能够加深我们对装饰者模式的理解。很多博客写不清楚,讲得很复杂,或者讲得很简单,很大原因是我们只看,而没有动手去做,动手去自己写,自己琢磨,就很容易能理解。这是学习方法,不是关注了公众号,看几篇文章就能轻松学会的,学习总是要自己动手才会理解深刻。看我的文章可以提供一些思路,更容易去上手。

总结

装饰者模式的优点:

  1. 可以动态地扩展类的功能,不会相互耦合。
  2. 符合开闭原则,利于代码维护。
  3. 比继承扩展的方式要更加灵活。

缺点:多层装饰,代码结构变得复杂。

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术爱好者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 通俗解释
  • 来个例子
  • 使用继承的方式实现
  • 使用装饰者模式实现
  • 装饰者模式与I/O流
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档