设计模式-装饰器模式
装饰器故名思义就是对原有的对象进行装饰,但与代理模式不同,代理模式是对功能进行业务无关的补充,而装饰器模式则是对原有对象功能的增强。
案例分析
一般说 Spring 的事务是动态代理,在事务执行前获取 sqlSession 首先关闭 autoCommit,执行事务操作,然后 commit,这些其实与真正的保存操作是没有关系的。
java
try {
session.setAutoCommit(false);
save();
commit();
} catch (Exception e) {
rollback();
} finally {
session.close;
}
但装饰器模式则更加强调对原有功能的增强,例如 Java 中某个文件读取只能每次读取 1 个字符,读取文件是需要 IO 操作的,会比较慢;如果有一片缓冲区,可以读取一定长度到缓存区,这样下次读字符时可以直接从缓冲区读取,这就是对字符型文件读取的增强。
java
// BufferedInputStream 源码
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
protected volatile byte[] buf;
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
}
但是 Java IO 使用的时候看起来可能有一点奇怪:
java
InputStream in = new FileInputStream("/");
BufferedInputStream inputStream = new BufferedInputStream(in);
需要先创建一个 FileInputStream 并传入 BufferedInputStream 中,那么为什么需要这样呢,InputStream 是一个抽象类,为什么不利用继承直接使用 InputStream 引用呢?就像下面这样
java
InputStream in = new BufferedInputStream("/");
如果这么实现,那么 BufferedInputStream 提供的是缓冲区读取功能,如果还有其他的功能,也需要继承 InputStream 类。如果一个新的类既支持缓冲区读取,有需要其他功能,就需要连续继承,这会使类之间的关系复杂且难以理解。
另一方面,使用这种组合的方式还有好处,如果一个类既需要支持 A 功能,有需要支持缓冲区读取,可以使用如下的代码:
java
InputStream in = new FileInputStream("/");
BufferedInputStream inputStream = new BufferedInputStream(in);
// 二次装饰 二次增强
DataInputStream stream = new DataInputStream(inputStream);
所有的 IO 读取类都继承 InputStream ,这样可以利用多态进行多重增强。
不过你如果实际去看这些 IO 类的源码,它们并非直接继承了 InputStream,而是继承了 FilterInputStream,InputStream 类中其实关于文件读取有默认实现,正常来讲继承的时候只需要对需要增强对地方增强,其他地方使用默认实现即可,但是对于文件操作来讲,默认实现是拿不到对于文件的真实句柄的,自然也无法进行读写,因此需要传入真正的 inputStream 操作文件。
例如 buffered 只针对缓存进行增强,如果需要关闭文件流,其实这个功能是不需要增强的,但是直接调用父类的方法拿不到文件句柄,就像下面这样,父类拿不到文件句柄自然也无法实现关闭操作
java
public void close() throws IOException {
super.close();
}
因此 Java IO 体系中设计了 FilterInputStream,在创建具体子类实例(例如 BufferedInputStream)时需要传入一个真正的 InputStream 对象,这个对象会被赋值在 FilterInputStream 中的属性中,这样子类在调用这些无需增强的方法时就不需要重写了。
java
protected FilterInputStream(InputStream in) {
this.in = in;
}
public void close() throws IOException {
in.close();
}
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据
。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者实现相同接口。