![]() |
相關閱讀 |
![]() |
>>> 技術話題—商業文明的嶄新時代 >>> | 簡體 傳統 |
2006.2.17 李建忠
对象容器的问题
在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器。
如果我们要对这样的对象容器进行处理:
上面是客户代码,客户代码里面必须要知道对象的结构,有可能还要使用递归的方法来处理这个对象,这样写耦合性就比较高。客户代码如果能只和IBox发生依赖就很好了,但是现在它还和ContainerBox和SingleBox发生了依赖,这样内部实现的细节就暴露给了外界,并且和外界产生了依赖关系。
动机(Motivation)
上述描述的问题根源在于:客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
意图(Intent)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
——《设计模式》GoF
例说Composite应用
以前面的例子为例
改进的方案
期望的客户代码:
接口和SingleBox代码都不变
ContainerBox代码变化
这样做ContainerBox里面的Process方法就不用判断是否是ContainerBox还是SingleBox,因为它们执行的方法名字都叫做Process。而且客户代码也只用调用box的Process方法即可。
但是这里还有一个问题,客户代码访问不了ContainerBox的Add和Remove方法,因为IBox接口里没有定义。
为了解决这个问题,我们可以选择在IBox接口里添加两个方法Add和Remove,然后SingleBox的Add和Remove方法什么都不做或者抛出异常。
但这样的处理方法也和理想的方法有点差距,因为IBox这个类并不符合我们类的单一职责原则,它有SingleBox和ContainerBox二者的职责,因此SingleBox对于Add和Remove也比较不好处理。但是总的来说,我们还是完成了客户代码的解耦工作。
我们看看整个代码的结构,ContainerBox里面包含了很多IBox,这些IBox有的是ContainerBox,有的也是SingleBox,因此它很像一个树形的结构。
结构(Structure)
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类。
它实际上是在Panel里面加了一个Controls属性,然后Controls属性是一个集合属性,它有Add和Remove方法。这样我们的IBox也可以改为:
在ASP.Net中就是这样,每一个控件都有Controls属性,也就是说每个控件都是一种容器控件(除了LiteralControl)。
这种方式把我们对安全性的担忧,统统放到容器(即ASP.Net中的Controls,以及例子中的Boxes)中去处理。
2010.10.3
MSDN 网络广播 李建忠 2013-08-22 08:45:58
稱謂:
内容: