eroself

关于人生的程式,在这里谱写......
  BlogJava :: 首页 :: 新随笔 :: 联系 ::  :: 管理

Extension Object/Interface模式

Posted on 2008-06-17 10:01 鬼谷子 阅读(387) 评论(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》




只有注册用户登录后才能发表评论。


网站导航: