源码中的设计模式–装饰器模式_在线工具

2022年5月9日09:06:13

一、模式入场

有一句很经典的小品台词是“换个马甲我就不认识你了吗”,哈哈,这个比方正好用在今天要分享的装饰器模式上。先看下《head first 设计模式》中给的释义。

装饰者模式  动态地将责任附加到对象上。若要扩展功能,装饰者提供了比基层更有弹性的替代方案。

细心的小伙伴发现了这个释义怎么是装饰者模式,今天说的不是装饰器模式吗,其实这两个名称所代表的意思是一样的,为了保持和书上一致这里是装饰者模式,后续统一称为装饰器。

这个释义太抽象太理论了,下面通俗的讲下。说到“装饰”二字,肯定第一时间想到的就是要有装饰者和被装饰者,正如前面说到的“换个马甲我就不认识你了吗”,这里的马甲可以理解为装饰者,穿马甲的就是被装饰者,放到装饰器模式里稍微有些不同,我们继续往下说。“装饰”,可以简单的理解为“伪装”,可以伪装成另外一个样子,也可以伪装成某种不同于原物的一种行为,所以在装饰者和被装饰者之间肯定存在某种相似,才可以使用装饰物去装饰被装饰者。用在java的设计模式中,我们讲的更多的是行为,也就是一个类所能完成的操作是可以用来装饰的。

下面简单的根据一个UML图来了解下装饰器模式,

源码中的设计模式--装饰器模式_在线工具

上图中Component是一个接口,有两个方法methodA、methodB,有三个实现类ComponentA、ComponentB、ComponentDecoratorA,可以看到ComponentDecoratorA和其他两个实现类是不一样的,它有一个Component的属性,其他的从UML中看不出其他,当然在methodA、methodB方法中别有洞天,后面会说到。这里的CompoentDecoratorA其实就是一个装饰器类,任何实现了Component接口的类,都可以被它装饰,完成相应的功能。

可以看到装饰器模式中有实现(继承),还有组合。

二、深入装饰器模式

上面对装饰器模式已经大体有了一个了解,下面通过一个具体的例子来实现一个简单的装饰器模式。

Component.java

package com.example.decorator;publicinterface Component {
    String methodA(String params);void methodB();
}

ComponentA.java

package com.example.decorator;publicclass ComponentAimplements Component{
    //返回字符串
    @Overridepublic String methodA(String params) {return "ComponentA methodA";
    }
    //打印字符串
    @Overridepublicvoid methodB() {
        System.out.println("ComponentA methodB");
    }
}

ComponentB.java

package com.example.decorator;publicclass ComponentBimplements Component{//返回字符串    @Overridepublic String methodA(String params) {return "ComponentB methodA";
    }//打印字符串    @Overridepublicvoid methodB() {
        System.out.println("ComponentB methodB");
    }
}

ComponentDecoratorA.java

package com.example.decorator;publicclass ComponentDecoratorAimplements Component{//Component的实例private Component component;//构造函数public ComponentDecoratorA(Component component){this.component=component;
    }//调用component的methodA方法,返回字符串    @Overridepublic String methodA(String params) {
        String decoratorStr=component.methodA(params);return "ComponentDecoratorA methodA,"+decoratorStr;
    }//调用component的methodB方法,打印字符串    @Overridepublicvoid methodB() {
        component.methodB();
        System.out.println("ComponentDecoratorA methodB");
    }
}

下面看测试代码,

package com.example.decorator;publicclass TestDecorator {publicstaticvoid 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 code0

符合测试预期。

很简单吧,这就是装饰器模式,总结以下要点,

1、装饰者和被装饰者要实现统一的接口;

2、在装饰者对象中持有被装饰者的对象实例;

3、在装饰者行为中,主动调用被装饰者行为;

很多小伙伴会问,装饰者和被装饰者必须实现统一的接口(interface)吗,使用抽象类可以吗,其实是可以的,上述的接口可以理解为接口和抽象类,我们说只要他们有共同的行为即可,不必太拘泥于定义。

另外,在装饰器模式中,运用了实现(继承)和组合设计原则

三、追寻源码

上面我们已经学会了使用装饰器模式,让我们继续在源码中找寻它的影子,学习下优秀的人是怎么使用装饰器模式的,让我们的代码越来越好。

1、java文件系统

在Java实现的API中已经有了装饰器模式的使用,而且在日常开发中很常用,不知道你注意到没有,如果没有下次在使用文件操作类的时候可以留心下哦。

在java的文件系统中,有字节流和字符流,又分为输入和输出,分别是InputStream、OutputStream、Reader、Writer。以InputStream来举例吧,在inputStream下有一个FilterInputStream,这是一个抽象类,该类便是一个装饰者类的接口,装饰所有实现了InputStream的类,

源码中的设计模式--装饰器模式_在线工具

另外,这里的InputStream是抽象类,

源码中的设计模式--装饰器模式_在线工具

看下其重要的read方法,在装饰者FilterInputStream中是怎么实现的,

源码中的设计模式--装饰器模式_在线工具

可以看到调用的是具体被装饰者的read方法,由于FilterInputStream是抽象的,我们看下其具体的一个实现类也就是具体的一个装饰者的实现,看下BufferedInputStream,

publicclass BufferedInputStreamextends FilterInputStream {//默认的缓冲大小privatestaticint DEFAULT_BUFFER_SIZE = 8192;//最大privatestaticint MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;//缓存区protectedvolatilebyte buf[];protectedint count;protectedint pos;protectedint markpos = -1;protectedint 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的实现,仅贴出部分代码,

publicclass LruCacheimplements Cache {//Cache实例privatefinal Cache delegate;//实现LRU算法的辅助mapprivate 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、在装饰者行为中,主动调用被装饰者行为;

装饰器模式很好的体现了继承(实现)和组合的设计原则。

源码中的设计模式--装饰器模式_在线工具

  • 作者:北漂程序员
  • 原文链接:https://www.cnblogs.com/teach/p/16214217.html
    更新时间:2022年5月9日09:06:13 ,共 4784 字。