装饰者模式实际上是一直提倡的组合代替继承的实践方式,个人认为要理解装饰者模式首先需要理解为什么需要组合代替继承,继承又是为什么让人深恶痛绝.
为什么建议使用组合代替继承?
面向对象的特性有继承与封装,但两者却又有一点矛盾,继承意味子类依赖了父类中的实现,一旦父类中改变实现则会对子类造成影响,这是打破了封装性的一种表现.
而组合就是巧用封装性来实现继承功能的代码复用.
举一个Effective Java中的案例,当前需求是为HashSet提供一个计数,要求统计它创建以来曾经添加了多少个元素,那么可以写出下面的代码.
public class InstrumentedHashSet <E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
this.addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
this.addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return this.addCount;
}
}
下面测试代码会抛出异常,正确结果是6,是不是匪夷所思,这种匪夷所思需要你去看HashSet
的具体实现,其addAll
实际上是调用了add
方法.
InstrumentedHashSet<String> hashSet = new InstrumentedHashSet<>();
hashSet.addAll(Arrays.asList("张三", "李四", "王二"));
Assert.assertEquals(hashSet.getAddCount(), 3);
这个案例说明了继承导致子类变得很脆弱,其不知道父类的细节,但是却实实在在的依赖了父类的实现.出现了问题也很难找出bug.
那么换成组合模式,让InstrumentedHashSet
持有HashSet
的私有实例,add以及addAll方法由HashSet
的私有实例代理执行.这就是组合所带来的优势,充分利用其它类的特点,降低耦合度,我只需要你已完成的功能,相比继承而并不受到你内部实现的制约.
public class InstrumentedHashSet <E>{
private int addCount = 0;
private HashSet<E> hashSet = new HashSet<>();
public boolean add(E e) {
this.addCount++;
return hashSet.add(e);
}
public boolean addAll(Collection<? extends E> c) {
this.addCount += c.size();
return hashSet.addAll(c);
}
public int getAddCount() {
return this.addCount;
}
}
装饰者模式
装饰者模式定义为:动态的给一对象添加一些额外的职责,对该对象进行功能性的增强.(只是增强,并没有改变使用原对象的意图)
装饰器模式类图:
以上是标准的装饰器模式,其中
AbstractDecorator
为一个装饰器模板,目的是为了提高代码复用,简化具体装饰器子类的实现成本,当然不需要的话也是可以省略的,其最主要的功能是持有了ComponentInterface
这个被装饰者对象,然后子类可以利用类似AOP环绕通知形式来在被装饰类执行sayHello()
前后执行自己的逻辑.这是装饰者模式的本质.
比如ContreteDecoratorA
增强了sayHello()
public class ContreteDecoratorA extends AbstractDecorator {
public ContreteDecoratorA(ComponentInterface componentInterface) {
super(componentInterface);
}
@Override
public void sayHello() {
System.out.println("A start");
super.sayHello();
System.out.println("A end");
}
}
具体使用方式
public static void main(String[] args) {
final ContreteDecoratorA decoratorA = new ContreteDecoratorA(new ComponentInterfaceImpl());
decoratorA.sayHello();
}
输出
A start
hello world
A end
其中默认实现ComponentInterfaceImpl
的sayHello()功能被装饰后增强.
Java I/O与装饰者
字节流
Java I/O框架就是一个很好的装饰者模式的实例.如下InputStream
关系图
其中
FileInputStream
,ObjectInputStream
等直接实现类提供了最基本字节流读取功能.而
FilterInputStream
作为装饰者,其内部引用了另一个InputStream
(实际被装饰的对象),然后以AOP环绕通知的形式来进行功能增强,笔者认为这里应该把该类定义为abstract更为合适.其承担的角色只是代码复用,帮助具体的装饰者类更加容易的实现功能增强.具体的装饰者
BufferedInputStream
为其他字节流提供了缓冲输入的支持.DataInputStream
则提供了直接解析Java原始数据流的功能.
由于装饰者模式的存在,原本一个字节一个字节读的FileInputStream
只需要嵌套一层BufferedInputStream
即可支持缓冲输入,
BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File("path")));
字符流
相比较字节流,字符流这边的关系则有点混乱,主要集中在BufferedReader
与FilterReader
,其两个角色都是装饰者,而FilterReader
是更加基本的装饰者其相对于字节流中的FilterInputStream
已经升级为abstract了,目的就是便于具体装饰者实现类更加容易的编写.那么为什么BufferedReader
不继承FilterReader
呢?这个问题暂时不知道答案,有兴趣的可以关注下知乎,等大牛回答.
为什么BufferedReader 不是 FilterReader的子类,而直接是Reader的子类?
不过从另一个角度来说,设计模式并不是套用模板,其最主要的是思想,对于装饰者模式最重要的是利用组合代替了继承,原有逻辑交给内部引用的类来实现,而自己只做增强功能,只要符合这一思想都可以称之为装饰者模式.
Mybatis与装饰者
Mybatis中有不少利用到装饰者模式,比如二级缓存Cache
,另外其Executor
也正在朝着装饰者模式改变.这里以Cache接口为主,类图如下:
从类图来看和装饰者模式似乎无半毛钱关系,实际上其省略了
AbstractDecorator
这一公共的装饰者基类.那么要实现装饰者其实现类中必须有一个Cache的被装饰对象,以LruCache为例.
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
@Override
public String getId() {
return delegate.getId();
}
....
}
其内部拥有Cache delegate
这一被装饰者,也就是无论什么Cache,只要套上了LruCache
那么就有了LRU这一特性.
在org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators
构造时则根据配置参数来决定增强哪些功能,下面代码则很好的体现了装饰者模式的优势,还望好好体会.
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
总结
装饰者模式实际上是继承的一种另类替代方式,以持有同类对象来达到继承的目的,同时由于多态的存在,使其比继承更加灵活多变.
对象包裹代理的过程可以理解为递归调用,其增强行为则类似AOP的环绕通知,理解了这些装饰者模式就很容易掌握了.