装饰器模式
由Java IO 类的一个用法讲起
- 如果我们需要打开文件从中读取数据,inputStream是一个抽象类。FileInputStream是专门读取文件流的子类。BufferedInputStram是一个支持带缓存的功能数据读取类,可以提高读取数据的效率。
InputStream in = new FileInputStream("/user/wangzheng/test.txt"); InputStream bin = new BufferedInputStream(in); byte[] data = new byte[128]; while (bin.read(data) != -1) { //... }
- 为什么要这么用?先创建一个FileInputStream,在去传递给一个BufferedInputStream。为什么不设计一个继承fileInputStream并且支持缓存的BufferedInputStream类对象呢?这样用起来岂不是更简单:
InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt"); byte[] data = new byte[128]; while (bin.read(data) != -1) { //... }
基于继承的设计方案
- 如果inputStream只有一个子类,FileInputStream,在他的基础上再来一个孙子类BufferedInputStream看起来并没有什么。类的层级结构比较简单,但是文章最开始的图里,实际上inputStream的子类有很多,我们如果需要支持Buffered,就需要给他的子类都去再派生一个BufferedXXX孙子类。
- 另外除了支持缓存,还要增强其他功能,例如DataInputStream支持按照基本数据类型来读取数据。这种情况下我们还需要再去对这个功能给每个子类派生出更多孙子类。因为Java的单继承限制,最终子类和孙子类的数量是非常庞大的。是功能数量的笛卡尔积。
- 如此多数量的子类难以维护和扩展。
基于装饰器模式的设计方案
- 可以使用组合代替继承。
- JavaIO的设计思路
public abstract class InputStream { //... public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { //... } public long skip(long n) throws IOException { //... } public int available() throws IOException { return 0; } public void close() throws IOException {} public synchronized void mark(int readlimit) {} public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } public boolean markSupported() { return false; } } public class BufferedInputStream extends InputStream { protected volatile InputStream in; protected BufferedInputStream(InputStream in) { this.in = in; } //...实现基于缓存的读数据接口... } public class DataInputStream extends InputStream { protected volatile InputStream in; protected DataInputStream(InputStream in) { this.in = in; } //...实现读取基本类型数据的接口 }
- 装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。
InputStream in = new FileInputStream("/user/wangzheng/test.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt();
- 装饰器是对功能的增强,这也是装饰器模式应用场景的一个重要特点。符合这种结构的设计模式有很多,比如代理模式、桥接模式。但是他们的设计意图不尽相同。代理模式给原始类(被修饰类)附加的是与原始类无关的功能,而在装饰器模式中,装饰器类附加的事跟原始类相关的增强功能。
// 代理模式的代码结构(下面的接口也可以替换成抽象类) public interface IA { void f(); } public class A impelements IA { public void f() { //... } } public class AProxy implements IA { private IA a; public AProxy(IA a) { this.a = a; } public void f() { // 新添加的代理逻辑 a.f(); // 新添加的代理逻辑 } } // 装饰器模式的代码结构(下面的接口也可以替换成抽象类) public interface IA { void f(); } public class A implements IA { public void f() { //... } } public class ADecorator implements IA { private IA a; public ADecorator(IA a) { this.a = a; } public void f() { // 功能增强代码 a.f(); // 功能增强代码 } }
- 实际上JDK源码中,BufferedInputStream、DataInputStream 并非继承自 InputStream,而是另外一个叫 FilterInputStream 的类。
- 对于BufferedInputStream他需要重新实现一遍InputStream的方法,即使不增强,也需要重新包裹调用,才能实现其真正的功能(委托给传进来的inputStream)。
public class BufferedInputStream extends InputStream { protected volatile InputStream in; protected BufferedInputStream(InputStream in) { this.in = in; } // f()函数不需要增强,只是重新调用一下InputStream in对象的f() public void f() { in.f(); } }
- 而DataInputStream也有同样问题,为了避免重复,JavaIO抽象出一个装饰器父类FilterInputStream,实现如下。他可以让InputStream的装饰器类继承自这个装饰器父类,这样装饰器泪痣需要实现它需要增强的方法即可,其他方法继承装饰器父类。
public class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } public int read() throws IOException { return in.read(); } public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public long skip(long n) throws IOException { return in.skip(n); } public int available() throws IOException { return in.available(); } public void close() throws IOException { in.close(); } public synchronized void mark(int readlimit) { in.mark(readlimit); } public synchronized void reset() throws IOException { in.reset(); } public boolean markSupported() { return in.markSupported(); } }
装饰器模式主要解决继承关系过于复杂的问题,通过组合替代继承。