eroself

          關(guān)于人生的程式,在這里譜寫......

          Extension Object/Interface模式

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

          Extension Object同Visitor、Decorator等模式差不多,都是在不改變類層次機(jī)構(gòu)的前提下向系統(tǒng)添加功能,被稱為擴(kuò)展對(duì)象模式。

          在了解擴(kuò)展對(duì)象模式之前需要了解一下相關(guān)的一條OOD原則,就是單一職責(zé)原則(Single Responsibility Principle,SRP)。

          就一個(gè)類而言,應(yīng)該僅有一個(gè)引起他變化的原因。

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

          在具體討論之前,先來(lái)看一個(gè)設(shè)計(jì)問(wèn)題。

          我們要開(kāi)始設(shè)計(jì)一個(gè)RSS Reader,一般使用一棵樹來(lái)呈現(xiàn)對(duì)于Feed的管理和組織結(jié)構(gòu),類似于Bloglines、GreatNews那樣的。構(gòu)建這棵樹的過(guò)程中,我們可以使用Composite模式來(lái)設(shè)計(jì)相應(yīng)的類層次結(jié)構(gòu),例如,基本接口我們可以采用

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

          然后構(gòu)建Composite的子類(用來(lái)容納目錄等組織結(jié)構(gòu))和具體類,例如

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

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

          對(duì)Extension的抽象封裝

          這個(gè)是Uncle Bob提過(guò)的一種優(yōu)雅的實(shí)現(xiàn)機(jī)制,是基于對(duì)于Extension的抽象封裝,然后在本身模型(上面的Channel組織模型)注冊(cè)這些 Extension,在運(yùn)行時(shí)可以方便的得到合適的擴(kuò)展來(lái)執(zhí)行相應(yīng)功能。例如我們來(lái)定義一個(gè)Extension的注冊(cè)和使用機(jī)制,然后在 IChannelNode中將請(qǐng)求delegate給這套機(jī)制,當(dāng)然也可以把這個(gè)直接集成到IChannelNode的代碼中,下面用Part和 PartExtension來(lái)完成這部分功能(代碼不是完整的)。

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

          可以簡(jiǎn)單的使用一個(gè)HashMap來(lái)完成extension的注冊(cè)和獲取。其實(shí)這部分的代碼就把擴(kuò)展這部分不變的內(nèi)容封裝起來(lái),而對(duì)于可能存在的變 化完全可以利用增加新的類來(lái)完成,也就滿足了開(kāi)放封閉原則(OCP)。比如我們說(shuō)到的persistent部分,建立相應(yīng)的persistent擴(kuò)展就可 以了。

          public class PersistentPart extends Part {
          public PersistentPart() {
          addExtension("CSVFormat", new CSVPersistentPartExtension());
          // 注冊(cè)其他format的存儲(chǔ)方式
          }
          }
          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();
          ......
          }
          }

          變化的部分就是用什么方式來(lái)存儲(chǔ)信息了,比如我們不使用CVS來(lái)存儲(chǔ)而改用XML存儲(chǔ),那么只需要生成相應(yīng)的XMLPersistentPartExtension,并把他注冊(cè)到PersistentPart就可以了。
          看了這個(gè),當(dāng)時(shí)我覺(jué)得搞得真是很麻煩,很復(fù)雜,而且可讀性很差啊,呵呵。不過(guò)本身Extension Object的實(shí)現(xiàn)就是特別復(fù)雜的,慢慢實(shí)踐,我想會(huì)越來(lái)越掌握這種方法的。

          我投入精力更大的是下面要說(shuō)的一種同Adapter結(jié)合起來(lái)的擴(kuò)展方法,并使用這種方法實(shí)現(xiàn)了UI部分的分離,感覺(jué)確實(shí)方便很多,不過(guò)開(kāi)始理解起來(lái)也是比較頭疼。

          使用Adapter模式來(lái)擴(kuò)展

          這里要說(shuō)的是Eclipse平臺(tái)底層最為核心的IAdaptable,Erich Gamma一手打造,我先后看了好久才體會(huì)到這種設(shè)計(jì)的魅力。這次擴(kuò)展我們拿UI來(lái)說(shuō)事。Eclipse中顯示一個(gè)Tree很容易,有了 Structured Model之后,打造好相應(yīng)的TreeContentProvider和LabelContentProvider就基本完成了。但是對(duì)于一個(gè)效果復(fù)雜的 顯示在Provider中的代碼就會(huì)顯的很臃腫,主要也是在LabelContentProvider中,而原有模型的邏輯也會(huì)增加很多基于UI部分的代 碼,比如Channel不同狀態(tài)下要顯示什么樣的圖片,使用什么樣的字體,什么樣的前景背景色等等。所以,比較好的辦法就是分離出一套并行的類層次, IChannelNodeUI,而且如果在擴(kuò)展過(guò)程中我們能夠把IChannelNode適配到IChannelNodeUI,并交給 LabelContentProvider就好辦了,所有的UI代碼就搞到IChannelNodeUI這套東西里面去了。
          現(xiàn)在介紹的擴(kuò)展方式就是依據(jù)Adapter為基礎(chǔ)的,所以,現(xiàn)在先把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;
          ... ...
          }
          ......
          }

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

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

          如果IChannelNode從IAdaptable繼承過(guò)來(lái),那么我們就可以在LabelContentProvider中這樣寫到(都沒(méi)有進(jìn)行check啊):

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

          那么就需要我們對(duì)于IChannelNode中的getAdapter來(lái)實(shí)現(xiàn),比如我們?cè)贑hannelNode中:

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

          這樣的問(wèn)題就是不好維護(hù),所以,Erich大牛還提供了對(duì)應(yīng)的IAdapterFactory和IAdapterManager,當(dāng)然AdapterManager是Platform中提供的對(duì)于框架部分的封裝,不需要我們干什么了。
          我們也就需要生成一個(gè)AdapterFactory,然后把他注冊(cè)給Platform的AdapterManager就可以了:

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

          那么如何使用呢,Eclipse的Platform里提供了一個(gè)PlatformObject的類,繼承他直接就具有了Adaptable的能力, 并自動(dòng)把getAdapter的請(qǐng)求轉(zhuǎn)交給Platform了,缺陷就是會(huì)影響你的繼承體系,畢竟Java無(wú)法多元繼承,所以,如果你不是繼承自 PlatformObject的話,自己轉(zhuǎn)交一下,

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

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

          參考數(shù)目:《敏捷軟件開(kāi)發(fā)》、《Contributing to Eclipse》



          主站蜘蛛池模板: 子长县| 凤山市| 阿巴嘎旗| 昂仁县| 乐昌市| 庄河市| 合江县| 车险| 安平县| 祁东县| 鹤庆县| 开远市| 泽库县| 遵义市| 长沙市| 通江县| 神农架林区| 普兰县| 麟游县| 北流市| 长沙市| 光泽县| 佛学| 韶关市| 大丰市| 哈巴河县| 长乐市| 阳泉市| 炎陵县| 柏乡县| 襄城县| 东乡族自治县| 名山县| 多伦县| 菏泽市| 柘城县| 高碑店市| 永泰县| 将乐县| 丰县| 龙游县|