源码中的设计模式–装饰器模式


一、模式入场

有一句很经典的小品台词是“换个马甲我就不认识你了吗”,哈哈,这个比方正好用在今天要分享的装饰器模式上。先看下《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、在装饰者行为中,主动调用被装饰者行为;

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

源码中的设计模式--装饰器模式

 

发表评论

相关文章