对装饰者模式的一个通俗的理解就是:一个东西A包装了另外一个东西B,A在B的功能基础上又扩展了新的功能,但是对外提供的接口不变
装饰者模式(Decorator)的定义:
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
通过使用装饰模式,可以在运行时扩充一个类的功能。原理是:增加一个装饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为装饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。装饰类必须和原来的类有相同的接口。
装饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。
当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类。相反,装饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。
装饰模式的类图:
装饰模式的实现:
public interface Component {
void doSomething();
}
public class ConcreteComponent implements Component {
@Override
public void doSomething() {
System.out.println("被装饰的原始类的代码");
}
}
public class Decorator implements Component {
private Component component;
@Override
public void doSomething() {
// component.doSomething();
//do nothing 需要在实现类来进行覆盖
}
public void setComponent(Component component) {
this.component = component;
}
protected Component getComponent() {
return component;
}
}
public class PrintLineDecorator extends Decorator {
public PrintLineDecorator(Component component) {
setComponent(component);
}
@Override
public void doSomething() {
System.out.println("在真正的调用之前打印一行");
getComponent().doSomething();
}
}
public class ComponentClient {
public static void main(String[] args) {
Component concreteComponent = new ConcreteComponent();
PrintLineDecorator printLineDecorator = new PrintLineDecorator(concreteComponent);
printLineDecorator.doSomething();
}
}
JDK中的装饰模式的分析
JDK中的BufferedInputStream和BufferedOoutputStream是典型的装饰者模式的实现
FilterInputStream是装饰者的父类,同时FilterInputStream中有一个InputStream类型的对象,指向实际需要被装饰的类,在子类中(BufferedInputStream等),在调用实际被装饰的类之前,或者之后,可以增加新的处理逻辑,我们看看BufferedInputStream是怎么做的
read()方法:
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
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;
}
整体的逻辑就是:读取的时候,先调用fill()将数据填充到buffer里,再将buffer里的数据返回,将数据填充到buffer的这个过程中,调用了原始类的read()方法到buffer
getInIfOpen().read(buffer, pos, buffer.length - pos);
为原本没有缓冲功能功的inputStream增加缓冲的功能,一次读取更多的数据,减少了读取的次数,提升了性能