COMPOSITE(组合)模式
1、意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
2、动机
在绘图编辑器和图形捕捉系统这样的图形应用程序中,用户可以使用简单的组件创建复杂的图表。用户可以组合多个简单的组件以形成一些较大的组件,这些组件又可以组合成更大的组件。一个简单的实现方法是为Text和Line这样的图元定义一些类,另外定义一些类作为这些图元的容器类。
然而这种方法存在一个问题:使用这些类的代码必须区别对待图元对象和容器对象,而大多数情况下用户认为他们是一样的。对这些类区别使用,使得程序更加复杂。
Composite模式描述了如何使用递归组合,使得用户不必对这些类进行区别,如下图所示:
Composite的关键是一个抽象类,它既可以代表图元又可以代表图元的容器。在图形系统中的这个类就是Graphic,它声明一些与特定图形对象相关的操作,例如Draw。同时它也声明了所有的组合对象共享的一些操作,例如一些操作用于访问和管理它的子部件。
子类Line、Rectangle和Text(参见前面的类图)定义了一些图元对象,这些类实现Draw,分别用于绘制直线、矩形和正文。由于图元都没有子类型,因此他们都不执行与子类有关的操作。
Picture类定义了一个Graphic对象的聚合。Picture的Draw操作是通过对它的子部件调用Draw实现的,Picture还用这种方法实现了一些与子部件相关的操作。由于Picture接口与Graphic接口是一致的,因此Picture对象可以递归地组合其它Picture对象。
下图是一个典型的由递归组合的Graphic对象组成的组合对象结构:
3、适用性
以下情况使用Composite模式
· 你想表示对象的部分-整体层次结构;
· 你希望用户忽略组合对象和单个对象的不同,用户将统一使用组合结构中的所有对象。
4、结构
典型的Composite对象结构如下图所示:
5、参与者
· Component(Graphic)
-- 为组合中的对象声明接口。
-- 在适当情况下,实现所有类共有接口的缺省行为。
-- 声明一个接口用户访问和管理Component的子组件。
-- (可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
· Leaf(Rectangle、Line、Text等)
-- 在组合中表示叶节点对象,叶节点没有子节点。
-- 在组合中定义图元对象的行为。
· Composite(Picture)
-- 定义有子部件的那些部件的行为。
-- 存储子部件。
-- 在Component接口中实现与子部件有关的操作。
· Client
-- 通过Component接口操纵组合部件的对象。
6、协作
用户使用Component类接口与组合结构中的对象进行交互。如果接受者是个叶节点,则直接处理请求。如果接受者是Composite,它通常将请求发送给它的子部件,在转发请求之前或之后可能执行一些辅助操作。
7、效果
Composite模式:
· 定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
· 简化客户代码 客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
· 使得更容易增加新类型的组件 新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。
· 使你的设计变得更加一般化 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
8、实现
我们在实现Composite模式时需要考虑以下几个问题:
1)显式的父部件引用 保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件的引用可以简化结构的上移和组件的删除,同时父部件引用也支持ChainOfResponsibility模式。
通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作。
对于父部件引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,而反之该组合以这些节点为子节点。保证这一点最容易的办法是,仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件。如果能在Composite类的Add和Remove操作中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式。
2)共享组件 共享组件是很有用的,比如它可以减少对存贮的需求。但是当一个组件只有一个父组件时,很难共享组件。
一个可行的解决办法是为子部件存贮多个父部件,但当一个请求在结构中向上传递时,这种方法会导致多义性。Flyweight模式讨论了如何修改设计以避免将父部件存贮在一起的方法。如果子部件可以将一些状态(或是所有的状态)存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。
3)最大化Component接口 Composite模式的目的之一是使得用户不知道他们正在使用的具体的Leaf和Composite类。为了达到这一目的,Composite类应为Leaf和Composite类尽可能多定义一些公共操作。Composite类通常为这些操作提供缺省的实现,而Leaf和Composite子类可以对他们进行重定义。
燃热,这个目标有时可能会与类层次结构设计原则相冲突,该原则规定:一个类只能定义那些对它的子类有意义的操作。有许多Component所支持的操作对Leaf类似乎没有什么意义,那么Component怎样为他们提供一个缺省的操作呢?
有时一点穿造型可以使得一个看起来仅对Composite才有意义的操作,将它移入Component类中,就会对所有的Component都适用。例如,访问子节点的接口是Composite类的一个基本组成部分,但对Leaf类来说并不必要。但是如果我们把一个Leaf看成一个没有子节点的Component就可以为这个Component类中定义一个缺省的操作,用于对子节点进行访问,这个缺省的操作不返回任何一个子节点。Leaf类可以适用缺省的实现,而Composite类则会重新实现这个操作以返回它们的子类。
管理子部件的操作比较复杂,我们将在下一项中予以讨论。
4)声明管理子部件的操作 虽然Composite类实现了Add和Remove操作用于管理子部件,但在Composite模式中一个重要的问题是:在Composite类层次结构中哪一些类声明这些操作。我们是应该在Component中声明这些操作,并使这些操作对Leaf类有意义呢,还是只应该在Composite和它的子类中声明并定义这些操作呢?
这需要在安全性和透明性之间做出权衡选择。
· 在类层次结构的根部定义子节点管理接口的方法具有良好的透明性,因为你可以一致地使用所有的组件,但是这一方法是以安全性为代价的,因为客户有可能会做一些无意义的事情,例如在Leaf中增加和删除对象等。
· 在Composite类中定义管理子部件的方法具有良好的安全性,因为在像C++这样的静态类语言中,在编译时任何从Leaf中增加或删除对象的尝试都将被发现。但是这又损失了透明性因为leaf和Composite具有不同的接口。
在这一模式中,相对于安全性,我们比较强调透明性。如果你选择了安全性,有时你可能会丢失类型信息,并且不得不将一个组件转换成一个组合。这样的类型转换必定不是类型安全的。
一种办法是在Component类中声明一个操作Composite* GetComposite()。Component提供了一个返回空指针的缺省操作。Composite类重新定义这个操作并通过this指针返回它自身。GetComposite允许你查询一个组件看它是否是一个组合,你可以对返回的组合安全地执行Add和Remove操作。
当然,这里的问题是我们对所有的组件的处理并不一致。在进行适当的动作之前,我们必须检测不同的类型。
提供透明性的唯一方法是在Component中定义缺省Add和Remove操作。这又带来了一个新的问题:Component::Add的实现不可避免地会有失败的可能性。你可以不让Component::Add做任何事情,但这就忽略了一个很重要的问题:企图向叶节点中增加一些东西时可能会引入错误。这时Add操作会产生垃圾。你可以让Add操作删除它的参数,但可能客户并不希望这样。
如果该组件不允许有子部件,或者Remove的参数不是该组件的子节点时,通常最好使用缺省方式(可能是产生一个异常)处理Add和Remove的失败。
另一个办法是对“删除”的含义做一些改变。如果该组件有一个父部件引用,我们可重新定义Component::Remove,在它的父组件中删除掉这个组件。然而,对应的Add操作仍然没有合理的解释。
5)Component是否应该实现一个Component列表 你可能希望在Component类中将子节点集合定义为一个实例变量,而这个Component类中也声明了一些操作对子节点进行访问和管理。但是在基类中存放子类指针,对叶节点来说会导致空间浪费,因为叶节点根本没有子节点。只有当该结构中子类数目相对较少时,才值得使用这种方法。
6)子部件排序 许多设计制定了Composite的子部件顺序。在前面的Graphics例子中,排序可能表示了从前至后的顺序。如果Composite表示语法分析树,Composite子部件的顺序必须反映程序结构,而组合语句就是这样一些Composite的实例。
如果需要考虑子节点的顺序时,必须仔细地设计对子节点的访问和管理接口,以便管理子节点序列。Iterator模式可以在这方面给予一些指导。
7)使用高速缓冲存贮改善性能 如果你需要对组合进行频繁的遍历或查找,Composite类可以缓冲存储对它的子节点进行遍历或查找的相关信息。Composite可以缓冲存储实际结构或者仅仅是一些用于缩短遍历或查询长度的信息。例如,动机一节的例子中Picture类能告诉缓冲存储其子部件的边界框,在绘图或选择期间,当子部件在当前窗口中不可见时,这个边界框使得Picture不需要再进行绘图或选择。
一个组件发生变化时,他的父部件原先缓冲存贮的信息也变得无效。在组件知道其父部件时,这种方法最为有效。因此,如果你使用告诉缓冲存贮,你需要定义一个接口来通知组合组件它们所缓冲存贮的信息无效。
8)应该由谁删除Component 在没有垃圾回收机制的语言中,当一个Composite被销毁时,通常最好由Composite负责删除其子节点。但有一种情况除外,即Leaf对象不会改变,因此可以被共享。
9)存贮组件最好用哪一种数据结构 Composite可使用多种数据结构存贮它们的子节点,包括连接列表、树、数组和hash表。数据结构的选择取决于效率。事实上,使用通用化素具结构根本没有必要。有时对每个子节点,Composite都有一个变量与之对应,这就要求Composite的每个子类都要实现自己的管理接口。参见Interpreter模式中的例子。
9、代码示例
网络上java的组合模式的代码示例有很多,可以随便参考。GOF的设计模式中使用的C++代码,此处不再列出。
COMPOSITE(组合)模式相关推荐
- 设计模式08: Composite 组合模式(结构型模式)
Composite 组合模式(结构型模式) 对象容器的问题 在面向对象系统中,我们常会遇到一类具有"容器"特征的对象--即他们在充当对象的同时,又是其他对象的容器. public ...
- java 组合代码_java实现Composite组合模式的实例代码
//20210121 写在前面:刚期末考试完,考了面向对象,里边儿有23个设计模式,我寻思着考完挨个儿实现一下,本文实现组合模式 组合模式核心思想类似文件夹的概念,构件树形结构,树形有叶子结点和文件夹 ...
- 组合的示例代码 java_java实现Composite组合模式的实例代码
//20210121 写在前面:刚期末考试完,考了面向对象,里边儿有23个设计模式,我寻思着考完挨个儿实现一下,本文实现组合模式 组合模式核心思想类似文件夹的概念,构件树形结构,树形有叶子结点和文件夹 ...
- C++设计模式-Composite组合模式
Composite组合模式 作用:将对象组合成树形结构以表示"部分-整体"的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. UML图如下: 在Compo ...
- Java 设计模式之 Composite 组合模式
Composite 组合模式相对简单,一般用于处理树状结构,递归结构,类似计算机中的文件系统,有文件夹,文件,文件夹里可以有文件:再比如一棵树,有枝干,叶子节点组成,枝干上又有叶子. 这里以构造一棵 ...
- 设计模式学习笔记(九)——Composite组合模式
Composite组合模式主要是应对这样的问题:一类具有"容器特征"的对象--即他们在充当对象的同时,又是其他对象的容器的情况.在编写时我们常常会造成:客户代码过多地依赖于对象容器 ...
- 设计模式(八)Composite(组合模式)
@TOC Composite(组合模式):将对象组合成树形结构以表示"部分-整体"的层次结构.它使得客户对单个对象和复合对象的使用具有一致性. 结构 意图 将对象组合成树形结构以表 ...
- C#面向对象设计模式第九讲:Composite 组合模式(结构型模式)
(根据MSDN Webcast相关课程整理) 由俄罗斯套娃讲起.娃娃里又包含另一个娃娃,最后那个不包含任何娃娃. 组合模式,采用树型结构来实现普遍存在的对象容器,将本原一对多的复杂的关系,转换成一对一 ...
- C++设计模式——Composite 组合模式
Composite objects into a tree structure so that individual objects and compositions of objects can b ...
- JAVA设计模式十七--Composite(组合模式)
组合模式 组合模式(Composite Pattern)有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念 ,客户程序可以向处理简单元素一样来处理复杂元素,从而使 ...
最新文章
- DataGridView使用技巧十:单元格表示值的自定义
- AopContext.currentProxy();为什么能获取到代理对象
- Zend Framework 多模块配置 (二)
- java获取mysql时间格式化_Java与mysql的时间格式化问题,获取时间的上下午
- 如何启动一个新的cmd窗口并在其内执行命令
- bash linux .ee,Linux下Bash shell学习笔记.md
- CentOs官网下载,CentOS国内镜像下载
- vscode下载历史版本
- 为解放程序员而生,网易重磅推“场景化云服务”,强势进军云计算市场
- 前端50个精美登录注册模板
- java中tab键_Java 9中的JShell中Tab键的用途是什么?
- java 面向对象三个特征_[Java] 面向对象的三个特征与含义
- october php,关于Laravel之October的安装配置过程
- 后台如何清理软Raid
- Photoshop制作漂亮白色荧光文字图片
- 写给区块链初创者的一封信
- 计算机开机硬件检查,开机bios检测硬件信息方法
- 大事件后台管理系统——个人中心
- Button 按钮的点击时候出现蓝色边框
- 蓝桥杯成绩公布啦,国赛将至?大一省一选手带你冲刺国赛