有一句很经典的小品台词是“换个马甲我就不认识你了吗”,哈哈,这个比方正好用在今天要分享的装饰器模式上。先看下《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)吗,使用抽象类可以吗,其实是可以的,上述的接口可以理解为接口和抽象类,我们说只要他们有共同的行为即可,不必太拘泥于定义。
另外,在装饰器模式中,运用了实现(继承)和组合设计原则。
上面我们已经学会了使用装饰器模式,让我们继续在源码中找寻它的影子,学习下优秀的人是怎么使用装饰器模式的,让我们的代码越来越好。
在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抽象类都是装饰器模式的体现,其抽象类的子类都是装饰者类。
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的缓存,你就可以自豪的说这是装饰器模式。
在mybatis中真正负责执行sql语句的是Executor接口,
该接口有以下几个实现类:CachingExecutor、BaseExecutor、SimpleExecutor等,重点看下CachingExecutor、SimpleExecutor
CachingExecutor应该是装饰者,看下SimpleExecutor
这个应该是被装饰者,它在执行具体的操作。
本文分享了装饰器模式及在源码中的使用,需要几种以下几点,
1、装饰者和被装饰者要实现统一的接口;
2、在装饰者对象中持有被装饰者的对象实例;
3、在装饰者行为中,主动调用被装饰者行为;
装饰器模式很好的体现了继承(实现)和组合的设计原则。