裝飾者模式實際上是一直提倡的組合代替繼承的實踐方式,個人認為要理解裝飾者模式首先需要理解為什么需要組合代替繼承,繼承又是為什么讓人深惡痛絕.
為什么建議使用組合代替繼承?
面向?qū)ο蟮奶匦杂欣^承與封裝,但兩者卻又有一點矛盾,繼承意味子類依賴了父類中的實現(xiàn),一旦父類中改變實現(xiàn)則會對子類造成影響,這是打破了封裝性的一種表現(xiàn).
而組合就是巧用封裝性來實現(xiàn)繼承功能的代碼復用.
舉一個Effective Java中的案例,當前需求是為HashSet提供一個計數(shù),要求統(tǒng)計它創(chuàng)建以來曾經(jīng)添加了多少個元素,那么可以寫出下面的代碼.
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;
}
}
下面測試代碼會拋出異常,正確結(jié)果是6,是不是匪夷所思,這種匪夷所思需要你去看HashSet
的具體實現(xiàn),其addAll
實際上是調(diào)用了add
方法.
InstrumentedHashSet<String> hashSet = new InstrumentedHashSet<>();
hashSet.addAll(Arrays.asList("張三", "李四", "王二"));
Assert.assertEquals(hashSet.getAddCount(), 3);
這個案例說明了繼承導致子類變得很脆弱,其不知道父類的細節(jié),但是卻實實在在的依賴了父類的實現(xiàn).出現(xiàn)了問題也很難找出bug.
那么換成組合模式,讓InstrumentedHashSet
持有HashSet
的私有實例,add以及addAll方法由HashSet
的私有實例代理執(zhí)行.這就是組合所帶來的優(yōu)勢,充分利用其它類的特點,降低耦合度,我只需要你已完成的功能,相比繼承而并不受到你內(nèi)部實現(xiàn)的制約.
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;
}
}
裝飾者模式
裝飾者模式定義為:動態(tài)的給一對象添加一些額外的職責,對該對象進行功能性的增強.(只是增強,并沒有改變使用原對象的意圖)
裝飾器模式類圖:
以上是標準的裝飾器模式,其中
AbstractDecorator
為一個裝飾器模板,目的是為了提高代碼復用,簡化具體裝飾器子類的實現(xiàn)成本,當然不需要的話也是可以省略的,其最主要的功能是持有了ComponentInterface
這個被裝飾者對象,然后子類可以利用類似AOP環(huán)繞通知形式來在被裝飾類執(zhí)行sayHello()
前后執(zhí)行自己的邏輯.這是裝飾者模式的本質(zhì).
比如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
其中默認實現(xiàn)ComponentInterfaceImpl
的sayHello()功能被裝飾后增強.
Java I/O與裝飾者
字節(jié)流
Java I/O框架就是一個很好的裝飾者模式的實例.如下InputStream
關系圖
其中
FileInputStream
,ObjectInputStream
等直接實現(xiàn)類提供了最基本字節(jié)流讀取功能.而
FilterInputStream
作為裝飾者,其內(nèi)部引用了另一個InputStream
(實際被裝飾的對象),然后以AOP環(huán)繞通知的形式來進行功能增強,筆者認為這里應該把該類定義為abstract更為合適.其承擔的角色只是代碼復用,幫助具體的裝飾者類更加容易的實現(xiàn)功能增強.具體的裝飾者
BufferedInputStream
為其他字節(jié)流提供了緩沖輸入的支持.DataInputStream
則提供了直接解析Java原始數(shù)據(jù)流的功能.
由于裝飾者模式的存在,原本一個字節(jié)一個字節(jié)讀的FileInputStream
只需要嵌套一層BufferedInputStream
即可支持緩沖輸入,
BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File("path")));
字符流
相比較字節(jié)流,字符流這邊的關系則有點混亂,主要集中在BufferedReader
與FilterReader
,其兩個角色都是裝飾者,而FilterReader
是更加基本的裝飾者其相對于字節(jié)流中的FilterInputStream
已經(jīng)升級為abstract了,目的就是便于具體裝飾者實現(xiàn)類更加容易的編寫.那么為什么BufferedReader
不繼承FilterReader
呢?這個問題暫時不知道答案,有興趣的可以關注下知乎,等大胖勘遥回答.
為什么BufferedReader 不是 FilterReader的子類,而直接是Reader的子類次和?
不過從另一個角度來說,設計模式并不是套用模板,其最主要的是思想,對于裝飾者模式最重要的是利用組合代替了繼承,原有邏輯交給內(nèi)部引用的類來實現(xiàn),而自己只做增強功能,只要符合這一思想都可以稱之為裝飾者模式.
Mybatis與裝飾者
Mybatis中有不少利用到裝飾者模式,比如二級緩存Cache
,另外其Executor
也正在朝著裝飾者模式改變.這里以Cache接口為主,類圖如下:
從類圖來看和裝飾者模式似乎無半毛錢關系,實際上其省略了
AbstractDecorator
這一公共的裝飾者基類.那么要實現(xiàn)裝飾者其實現(xiàn)類中必須有一個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();
}
....
}
其內(nèi)部擁有Cache delegate
這一被裝飾者,也就是無論什么Cache,只要套上了LruCache
那么就有了LRU這一特性.
在org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators
構造時則根據(jù)配置參數(shù)來決定增強哪些功能,下面代碼則很好的體現(xiàn)了裝飾者模式的優(yōu)勢,還望好好體會.
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);
}
}
總結(jié)
裝飾者模式實際上是繼承的一種另類替代方式,以持有同類對象來達到繼承的目的,同時由于多態(tài)的存在,使其比繼承更加靈活多變.
對象包裹代理的過程可以理解為遞歸調(diào)用,其增強行為則類似AOP的環(huán)繞通知,理解了這些裝飾者模式就很容易掌握了.