設計模式--裝飾者模式思考

裝飾者模式實際上是一直提倡的組合代替繼承的實踐方式,個人認為要理解裝飾者模式首先需要理解為什么需要組合代替繼承,繼承又是為什么讓人深惡痛絕.

為什么建議使用組合代替繼承?

面向?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)的給一對象添加一些額外的職責,對該對象進行功能性的增強.(只是增強,并沒有改變使用原對象的意圖)
裝飾器模式類圖:

image

以上是標準的裝飾器模式,其中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關系圖

image

其中FileInputStream,ObjectInputStream等直接實現(xiàn)類提供了最基本字節(jié)流讀取功能.
FilterInputStream作為裝飾者,其內(nèi)部引用了另一個InputStream(實際被裝飾的對象),然后以AOP環(huán)繞通知的形式來進行功能增強,筆者認為這里應該把該類定義為abstract更為合適.其承擔的角色只是代碼復用,幫助具體的裝飾者類更加容易的實現(xiàn)功能增強.
image

具體的裝飾者BufferedInputStream為其他字節(jié)流提供了緩沖輸入的支持.DataInputStream則提供了直接解析Java原始數(shù)據(jù)流的功能.

由于裝飾者模式的存在,原本一個字節(jié)一個字節(jié)讀的FileInputStream只需要嵌套一層BufferedInputStream即可支持緩沖輸入,

    BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File("path")));

字符流

相比較字節(jié)流,字符流這邊的關系則有點混亂,主要集中在BufferedReaderFilterReader,其兩個角色都是裝飾者,而FilterReader是更加基本的裝飾者其相對于字節(jié)流中的FilterInputStream已經(jīng)升級為abstract了,目的就是便于具體裝飾者實現(xiàn)類更加容易的編寫.那么為什么BufferedReader不繼承FilterReader呢?這個問題暫時不知道答案,有興趣的可以關注下知乎,等大胖勘遥回答.
為什么BufferedReader 不是 FilterReader的子類,而直接是Reader的子類次和?

不過從另一個角度來說,設計模式并不是套用模板,其最主要的是思想,對于裝飾者模式最重要的是利用組合代替了繼承,原有邏輯交給內(nèi)部引用的類來實現(xiàn),而自己只做增強功能,只要符合這一思想都可以稱之為裝飾者模式.


image

Mybatis與裝飾者

Mybatis中有不少利用到裝飾者模式,比如二級緩存Cache,另外其Executor也正在朝著裝飾者模式改變.這里以Cache接口為主,類圖如下:

image

從類圖來看和裝飾者模式似乎無半毛錢關系,實際上其省略了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)繞通知,理解了這些裝飾者模式就很容易掌握了.

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栏尚,一起剝皮案震驚了整個濱河市摆舟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖隙轻,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垢揩,居然都是意外死亡玖绿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門叁巨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斑匪,“玉大人,你說我怎么就攤上這事锋勺∈慈常” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵庶橱,是天一觀的道長贮勃。 經(jīng)常有香客問我,道長苏章,這世上最難降的妖魔是什么寂嘉? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮枫绅,結(jié)果婚禮上泉孩,老公的妹妹穿的比我還像新娘。我一直安慰自己并淋,他們只是感情好棵譬,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著预伺,像睡著了一般订咸。 火紅的嫁衣襯著肌膚如雪曼尊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天脏嚷,我揣著相機與錄音骆撇,去河邊找鬼。 笑死父叙,一個胖子當著我的面吹牛神郊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趾唱,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼涌乳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甜癞?” 一聲冷哼從身側(cè)響起夕晓,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悠咱,沒想到半個月后蒸辆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡析既,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年躬贡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眼坏。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡拂玻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宰译,到底是詐尸還是另有隱情纺讲,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布囤屹,位于F島的核電站,受9級特大地震影響逢渔,放射性物質(zhì)發(fā)生泄漏肋坚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一肃廓、第九天 我趴在偏房一處隱蔽的房頂上張望智厌。 院中可真熱鬧,春花似錦盲赊、人聲如沸铣鹏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诚卸。三九已至葵第,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間合溺,已是汗流浹背卒密。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棠赛,地道東北人哮奇。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像睛约,于是被迫代替她去往敵國和親鼎俘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容