eroself

          關于人生的程式,在這里譜寫......
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 ::  :: 管理

          Extension Object/Interface模式

          Posted on 2008-06-17 10:01 鬼谷子 閱讀(408) 評論(0)  編輯  收藏 所屬分類: Java
          轉自:http://xerdoc.com/blog/archives/228.html

          Extension Object同Visitor、Decorator等模式差不多,都是在不改變類層次機構的前提下向系統添加功能,被稱為擴展對象模式。

          在了解擴展對象模式之前需要了解一下相關的一條OOD原則,就是單一職責原則(Single Responsibility Principle,SRP)。

          就一個類而言,應該僅有一個引起他變化的原因。

          大致的意思就是要把職責分配到不同的對象當中去。如果我們把每一個職責的變化看成是變化的一個軸線,當需求變化時,該變化會反映為類的職責的變化。 如果一個類承擔了多余一個的職責,那么引起他變化的原因就多余一個軸線,也就是把這些職責耦合到了一起。在某種程度上,這種耦合會導致Fragile Design,當變化發生時,設計會遭受到意向不到的破壞。

          在具體討論之前,先來看一個設計問題。

          我們要開始設計一個RSS Reader,一般使用一棵樹來呈現對于Feed的管理和組織結構,類似于Bloglines、GreatNews那樣的。構建這棵樹的過程中,我們可以使用Composite模式來設計相應的類層次結構,例如,基本接口我們可以采用

          public interface IChannelNode {
          public String getTitle();
          public String getId();
          ... ...
          }

          然后構建Composite的子類(用來容納目錄等組織結構)和具體類,例如

          public class CompositeChannelNode implements IChannelNode {
          public void addChild(IChannelNode node);
          ... ...
          }
          public class FeedChannelNode implements IChannelNode {
          ... ...
          }

          上述這些類結構完全可以用來描述這棵樹的層次結構了,而且我們可以使用Eclipse中的TreeViewer來顯示。Eclipse中的 TreeViewer是基于Structured Model的,所以,幾乎使用很少的代碼,就可以完成顯示這個樹的功能,這也是Eclipse對于這種基于Structured Model封裝的比較好的結果。不過簡單的使用UI會比較單調,所以,就需要對于UI部分進行潤色,那么在這個類層次中不可避免的要加入UI部分的代碼, 這就不符合SRP的要求了。另外,假如我們還要完成對于這棵樹的persistent,那么序列化的代碼又會污染這個單純的模型了。這里我們就可以使用擴 展對象模式了,把這些變化另外wrap起來。

          對Extension的抽象封裝

          這個是Uncle Bob提過的一種優雅的實現機制,是基于對于Extension的抽象封裝,然后在本身模型(上面的Channel組織模型)注冊這些 Extension,在運行時可以方便的得到合適的擴展來執行相應功能。例如我們來定義一個Extension的注冊和使用機制,然后在 IChannelNode中將請求delegate給這套機制,當然也可以把這個直接集成到IChannelNode的代碼中,下面用Part和 PartExtension來完成這部分功能(代碼不是完整的)。

          public abstract class Part  {
          public void addExtension(String extensionType, PartExtension extension) { ... ... }
          public PartExtension getExtension(String extensionType) { ... ... }
          }
          public interface PartExtension {
          }

          可以簡單的使用一個HashMap來完成extension的注冊和獲取。其實這部分的代碼就把擴展這部分不變的內容封裝起來,而對于可能存在的變 化完全可以利用增加新的類來完成,也就滿足了開放封閉原則(OCP)。比如我們說到的persistent部分,建立相應的persistent擴展就可 以了。

          public class PersistentPart extends Part {
          public PersistentPart() {
          addExtension("CSVFormat", new CSVPersistentPartExtension());
          // 注冊其他format的存儲方式
          }
          }
          public interface PersistentExtension extends PartExtension {
          public void doPersistent(IChannelNode node);
          }
          public class CSVPersistentPartExtension implements PersistentExtension {
          public void doPersistent(IChannelNode node) {
          // save node.getTitle();
          // save node.getId();
          ......
          }
          }

          變化的部分就是用什么方式來存儲信息了,比如我們不使用CVS來存儲而改用XML存儲,那么只需要生成相應的XMLPersistentPartExtension,并把他注冊到PersistentPart就可以了。
          看了這個,當時我覺得搞得真是很麻煩,很復雜,而且可讀性很差啊,呵呵。不過本身Extension Object的實現就是特別復雜的,慢慢實踐,我想會越來越掌握這種方法的。

          我投入精力更大的是下面要說的一種同Adapter結合起來的擴展方法,并使用這種方法實現了UI部分的分離,感覺確實方便很多,不過開始理解起來也是比較頭疼。

          使用Adapter模式來擴展

          這里要說的是Eclipse平臺底層最為核心的IAdaptable,Erich Gamma一手打造,我先后看了好久才體會到這種設計的魅力。這次擴展我們拿UI來說事。Eclipse中顯示一個Tree很容易,有了 Structured Model之后,打造好相應的TreeContentProvider和LabelContentProvider就基本完成了。但是對于一個效果復雜的 顯示在Provider中的代碼就會顯的很臃腫,主要也是在LabelContentProvider中,而原有模型的邏輯也會增加很多基于UI部分的代 碼,比如Channel不同狀態下要顯示什么樣的圖片,使用什么樣的字體,什么樣的前景背景色等等。所以,比較好的辦法就是分離出一套并行的類層次, IChannelNodeUI,而且如果在擴展過程中我們能夠把IChannelNode適配到IChannelNodeUI,并交給 LabelContentProvider就好辦了,所有的UI代碼就搞到IChannelNodeUI這套東西里面去了。
          現在介紹的擴展方式就是依據Adapter為基礎的,所以,現在先把Adapter這部分搞定

          public interface IChannelNodeUI {
          public Image getImage();
          public Font getFont();
          ... ...
          }
          public class ChannelNodeUIAdapter implements IChannelNodeUI {
          private IChannelNode node_;
          public ChannelNodeUIAdapter(IChannelNode node) { ... ... }

          public Image getImage() {
          if (node_.isLoading())
          // return loading image;
          ... ...
          }
          ......
          }

          接下來也就是對這些擴展的管理,比如我們還有對于IPersistent的適配,那么如何更好的來管理這些擴展呢。比較容易想到的還是注冊然后獲取這樣的方法,可是這些適配器和適配關系如何管理呢?
          看一下,Erich大牛的IAdaptable

          org.eclipse.core.runtime/IAdaptable
          public interface IAdaptable {
          public Object getAdapter(Class adapter);
          }

          如果IChannelNode從IAdaptable繼承過來,那么我們就可以在LabelContentProvider中這樣寫到(都沒有進行check啊):

              public Image getImage(Object element) {
          IChannelNode node = (IChannelNode)element;
          IChannelNodeUI uiNode = (IChannelNodeUI)node.getAdapter(IChannelNodeUI.class);
          return uiNode.getImage();
          }

          那么就需要我們對于IChannelNode中的getAdapter來實現,比如我們在ChannelNode中:

              public Object getAdapter(Class adapter) {
          if(adapter == IChannelNodeUI.class)
          return new ChannelNodeUIAdapter(this);
          return null;
          }

          這樣的問題就是不好維護,所以,Erich大牛還提供了對應的IAdapterFactory和IAdapterManager,當然AdapterManager是Platform中提供的對于框架部分的封裝,不需要我們干什么了。
          我們也就需要生成一個AdapterFactory,然后把他注冊給Platform的AdapterManager就可以了:

              IAdapterManager manager = Platform.getAdapterManager();
          IAdapterFactory uiFactory = new ChannelNodeUIFactory();
          manager.registerAdapters(uiFactory, IChannelNode.class);

          那么如何使用呢,Eclipse的Platform里提供了一個PlatformObject的類,繼承他直接就具有了Adaptable的能力, 并自動把getAdapter的請求轉交給Platform了,缺陷就是會影響你的繼承體系,畢竟Java無法多元繼承,所以,如果你不是繼承自 PlatformObject的話,自己轉交一下,

              Platform.getAdapterManager().getAdapter(this, IChannelNodeUI.class);

          這種擴展對象模式就是基于這樣的Adapter模式,由IAdaptable、IAdapterFactory和IAdapterManager完成了對框架的封裝,從而給Eclipse帶來了巨大的靈活性。

          參考數目:《敏捷軟件開發》、《Contributing to Eclipse》



          主站蜘蛛池模板: 满城县| 松桃| 承德县| 米林县| 腾冲县| 阳新县| 河北区| 泰顺县| 宜良县| 米林县| 闻喜县| 原平市| 巢湖市| 内江市| 邛崃市| 平顶山市| 鲜城| 城步| 宁河县| 柯坪县| 合江县| 霍邱县| 土默特左旗| 柳州市| 吉安县| 响水县| 阿克陶县| 微博| 韶山市| 宾川县| 呼和浩特市| 天祝| 德化县| 南充市| 宁夏| 密山市| 涪陵区| 定襄县| 隆化县| 安福县| 孟州市|