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》