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

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

2006.2.17 李建忠

对象容器的问题

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

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)中去处理。

2010.10.3


MSDN 网络广播 李建忠 2013-08-22 08:45:58

[新一篇] C#面向對象設計模式縱橫談 第8講:Bridge 橋接模式

[舊一篇] C#面向對象設計模式縱橫談 第10講:Decorator 裝飾模式
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表