您的位置:知识库 » 软件设计

C#面向对象设计模式纵横谈:Composite 组合模式

作者: 山天大畜  来源: 博客园  发布时间: 2010-11-03 17:46  阅读: 2099 次  推荐: 0   原文链接   [收藏]  

  对象容器的问题

  在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器。

image

  如果我们要对这样的对象容器进行处理:

image

  上面是客户代码,客户代码里面必须要知道对象的结构,有可能还要使用递归的方法来处理这个对象,这样写耦合性就比较高。客户代码如果能只和IBox发生依赖就很好了,但是现在它还和ContainerBox和SingleBox发生了依赖,这样内部实现的细节就暴露给了外界,并且和外界产生了依赖关系。

  动机(Motivation)

  上述描述的问题根源在于:客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

  意图(Intent)

  将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

                                              ——《设计模式》GoF

  例说Composite应用

  以前面的例子为例

image

image

  改进的方案

  期望的客户代码:

image

  接口和SingleBox代码都不变

image

image

  ContainerBox代码变化

image

  这样做ContainerBox里面的Process方法就不用判断是否是ContainerBox还是SingleBox,因为它们执行的方法名字都叫做Process。而且客户代码也只用调用box的Process方法即可。但是这里还有一个问题,客户代码访问不了ContainerBox的Add和Remove方法,因为IBox接口里没有定义。

  为了解决这个问题,我们可以选择在IBox接口里添加两个方法Add和Remove,然后SingleBox的Add和Remove方法什么都不做或者抛出异常。

image

  但这样的处理方法也和理想的方法有点差距,因为IBox这个类并不符合我们类的单一职责原则,它有SingleBox和ContainerBox二者的职责,因此SingleBox对于Add和Remove也比较不好处理。但是总的来说,我们还是完成了客户代码的解耦工作。

  我们看看整个代码的结构,ContainerBox里面包含了很多IBox,这些IBox有的是ContainerBox,有的也是SingleBox,因此它很像一个树形的结构。

  结构(Structure)

image

  Component抽象类或者接口对应之前例子中的IBox,Leaf对应SingleBox,Composite对应ContainerBox。客户代码只依赖于Component抽象类或者结构,这正是我们期望的目的。

  Composite模式的几个要点

  Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。

  将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。

  Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.Net控件的实现在这方面为我们提供了一个很好的示范。

  Composite模式在具体实现中,可以让父对象中的子对象反向追朔;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

  .NET框架中的Composite应用

  ASP.Net中的Panel对象就是一个Composite对象,而Button对象就是Leaf对象。Button和Panel都继承自System.Web.UI.Control类。

image

  它实际上是在Panel里面加了一个Controls属性,然后Controls属性是一个集合属性,它有Add和Remove方法。这样我们的IBox也可以改为:

image

  在ASP.Net中就是这样,每一个控件都有Controls属性,也就是说每个控件都是一种容器控件(除了LiteralControl)。这种方式把我们对安全性的担忧,统统放到容器(即ASP.Net中的Controls,以及例子中的Boxes)中去处理。

0
0

软件设计热门文章

    软件设计最新文章

      最新新闻

        热门新闻