2 基于装饰器模式设计Java IO
如果基于装饰器模式设计Java IO就会很灵活,需要什么功能就去装饰什么功能,还可以实现嵌套装饰
抽象组件
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 { //... } //其它方法不详细展开 }
具体组件
class FileInputStream extends InputStream { public FileInputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null); } public FileInputStream(File file) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(name); } if (name == null) { throw new NullPointerException(); } if (file.isInvalid()) { throw new FileNotFoundException("Invalid file path"); } fd = new FileDescriptor(); fd.attach(this); path = name; open(name); } public int read() throws IOException { return read0(); } private native int read0() throws IOException; private native int readBytes(byte b[], int off, int len) throws IOException; public int read(byte b[]) throws IOException { return readBytes(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return readBytes(b, off, len); } //其它方法不详细展开 }
抽象装饰器
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(); } }
为什么需要FilterInputStream这个抽象装饰器呢?
- InputStream 是一个抽象类而非接口,而且它的大部分函数(比如 read()、available())都有默认实现,理论上只需要在 BufferedInputStream 类中重新实现那些需要增加缓存功能的函数就可以了,其他函数继承 InputStream 的默认实现。但实际上,这样做是行不通的,对于即便是不需要增加缓存功能的函数来说,BufferedInputStream 还是必须把它重新实现一遍,简单包裹对 InputStream 对象的函数调用。如果不重新实现,那 BufferedInputStream 类就无法将最终读取数据的任务,委托给传递进来的 InputStream 对象来完成,因为构造方法的参数类型是个抽象类,那传递进来的就可能是它的子类,运行时同样的方法名不见得用的是同一个方法实现,如果不委托,那么运行的永远是抽象类的方法实现而不能使用其子类的增强实现了。
- 你可能会问既然我不增强这个方法用默认的抽象方法不行么?如果是单层装饰这样看起来没问题,单层装饰我们能明确自己要增强的是哪个方法。但如果是嵌套装饰呢?
new CheeseDecorator(new CreamDecorator(cakeMaker))
,CheeseDecorator
装饰了CreamDecorator
,被装饰者CreamDecorator
其实也是一个装饰者,CheeseDecorator
和CreamDecorator
如果增强的方法不完全一致,例如它们共同增强了cakeMake
方法,这也是嵌套装饰的目的。但是CheeseDecorator
的方法fun()
没有增强,CreamDecorator
进行了增强,CheeseDecorator
如果不对fun()
进行委托实现,那么其实就丢掉了被装饰类CreamDecorator
对fun()
的增强,这样是不合理的。 - 综合以上,又为了避免代码重复,Java IO 抽象出了一个装饰器父类 FilterInputStream。InputStream 的所有的装饰器类(BufferedInputStream、DataInputStream)都继承自这个装饰器父类。这样,装饰器类只需要实现它需要增强的方法就可以了,其他方法继承装饰器父类的默认实现
说白了这个装饰器父类是用来做调用传递转发功能的,让装饰器子类能更聚焦于自己增强的功能。
具体装饰器
public class BufferedInputStream extends FilterInputStream{ protected volatile InputStream in; public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } } //其它方法不详细展开 } public class DataInputStream extends FilterInputStream implements DataInput { public DataInputStream(InputStream in) { super(in); } //其它方法不详细展开 }
3 Java IO装饰器模式实现源码分析
客户端调用代码如下:
//它既支持缓存读取,又支持按照基本数据类型来读取数据 InputStream in = new FileInputStream("/user/test.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt();
客户端调用时首先执行din.readInt
方法:
public final int readInt() throws IOException { int ch1 = in.read(); int ch2 = in.read(); int ch3 = in.read(); int ch4 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); }
这里方法内部使用了in.read()
,这个in就来自被装饰的对象:bin:
class DataInputStream extends FilterInputStream implements DataInput { /** * Creates a DataInputStream that uses the specified * underlying InputStream. * * @param in the specified input stream */ public DataInputStream(InputStream in) { super(in); } }
所以执行的是BufferedInputStream
的read
方法,通过缓存读取数据:
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }
这里使用了fill()
方法,fill方法的执行内容如下:
private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* grow buffer */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }
我们继续向下跟看到一个getInIfOpen().read(buffer, pos, buffer.length - pos)
调用,我们看看getInIfOpen
方法:
private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; }
这里用的in
对象就来自于FileInputStream
对象,在new BufferedInputStream
时候将构造函数传递的in赋给了成员变量in
public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
super(in)
其实就是调用了原始的FilterInputStream
的构造函数,将参数in赋予成员变量in
class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } }
这样就通过双重装饰,将普通的文件流改装为可以进行缓存读和基础数据读的文件流。
模式对比
这里我们比较下装饰器模式和代理模式:
代理模式和装饰器模式
让别人帮助你做你并不关心的事情,叫代理模式;为让自己的能力增强,使得增强后的自己能够使用更多的方法,拓展在自己基础之上的功能的,叫装饰器模式
对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:
- 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
- 装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;
- 装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;
核心的区别可以这么理解:装饰器模式是增强原有对象功能,是纵向继承的一个代替方案。代理模式是在原有对象的功能前后进行一些业务无关的AOP处理,例如记录日志等,简而言之,一个纵向,一个横向。
模式扩展
装饰器模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况:
如果只有一个具体组件而没有抽象组件时,可以让抽象装饰继承具体组件
如果只有一个具体装饰器,可以将抽象装饰器和具体装饰器合并
这种结构就非常像代理模式了,唯一的区别就是具体装饰持有的引用是抽象组件,而不是像代理对象持有的是具体目标对象的引用而已。这就引出了它们非常大的一个区别:装饰模式的功能实现是由上层调用者决定的,而代理模式的功能已经写死了,不能够实现灵活的拓展,从结构上甚至可以简单的理解为装饰模式是代理模式的超集,但从使用角度看完全是两回事儿。
总结一下
装饰器模式和代理模式其实很容易混了,说实在的单从角色关系结构上来说也非常相似。但是我们从使用角度出发去看还是有很多差别的,代理模式解决的问题是对当前功能的横向扩展,所以每个代理类要横向扩展的功能是固定的,例如增加日志,所以设计上不存在也没有必要定义抽象代理类,横向功能是不需要也不可能穷尽收束的。而装饰者模式被设计来是用来替代继承实现类功能增强的一种方案,设计上存在抽象装饰类,以便通过各种具体装饰子类对具体组件实现多样的功能增强,对一个具体组件来说我们是可以给它定制装饰方案的,所以通过抽象装饰类来实现。