组合模式

定义: 将对象组合成树形结构以表示"部分-整体"的层次结构.组合模式使得用户对待单个对象或组合对象的使用具有一致性

对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象)并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理。由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下我们希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。组合模式为解决此类问题而诞生,它可以让叶子对象和容器对象的使用具有一致性

举个例子:文件夹目录结构,族谱,公司结构,货物管理结构等等

我们可以看出在第一张图中,包含文件和文件夹两类不同的元素,其中在文件夹中可以包含文件,还可以继续包含子文件夹,但是文件中不能再包含文件或者子文件夹.在此,我们可以称文件夹为容器(Container),而不同类型的各种文件是其成员,也称为叶子(Leaf),一个文件夹也可以作为另一个更大的文件夹的成员.如果我们需要对某一个文件夹进行操作,如查找文件,那么需要对指定的文件夹进行遍历,如果存在子文件夹则打开其子文件夹继续遍历,如果是文件则判断之后返回查找结果

模式结构和说明

在组合模式结构图中包含如下几个角色:

  • Component(抽象构件):它可以是接口或者抽象类,为叶子结构和容器结构对象声明接口,在该角色中可以包含所有子类共有的声明和实现.在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件,删除子构件,获取子构件等
  • Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包含那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法
  • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为.对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

组合模式的关键是定义了一个抽象构建类,它既可以代表叶子,又可以代表容器,而客户端对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理.容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构

下面通过简单的示例代码来分析组合模式的各个角色的用途和实现

简单示例说明

抽象构件角色:

abstract class Component {  public abstract void add(Component c); //增加成员  public abstract void remove(Component c); //删除成员  public abstract Component getChild(int i); //获取成员  public abstract void operation();  //业务方法
}
复制代码

一般将抽象构件类设计为接口或者抽象类,将所有子类共有方法的声明和实现放在抽象构件类中.对于客户端而言,将针对抽象构件编程,而无须关心起具体子类是容器构件还是叶子构件

如果继承抽象构件的是叶子构件,代码如下:

class Leaf extends Component {public void add(Component c) { //异常处理或错误提示 }   public void remove(Component c) { //异常处理或错误提示 }public Component getChild(int i) { //异常处理或错误提示return null; }public void operation() {//叶子构件具体业务方法的实现}
}
复制代码

作为抽象构件类的子类,在叶子构件中需要实现在抽象构件类中声明的所有方法,包括业务方法以及管理和访问子构件的方法,但是叶子构件不能再包含子构件,因此在叶子构件中实现子构件管理和访问方法时需要提供异常处理或错误提示。

如果继承抽象构件的是容器构件,则其典型代码如下所示:

class Composite extends Component {private ArrayList<Component> list = new ArrayList<Component>();public void add(Component c) {list.add(c);}public void remove(Component c) {list.remove(c);}public Component getChild(int i) {return (Component)list.get(i);}public void operation() {//容器构件具体业务方法的实现//递归调用成员构件的业务方法for(Object obj:list) {((Component)obj).operation();}}
}
复制代码

在容器构件中实现了在抽象构件中声明的所有方法,既包括业务方法,也包括用于访问和管理成员子构件的方法,如 add()、remove() 和 getChild() 等方法。需要注意的是在实现具体业务方法时,由于容器构件充当的是容器角色,包含成员构件,因此它将调用其成员构件的业务方法。在组合模式结构中,由于容器构件中仍然可以包含容器构件,因此在对容器构件进行处理时需要使用递归算法,即在容器构件的 operation() 方法中递归调用其成员构件的 operation() 方法。

模式讲解

认识组合模式

1.组合模式的目的

让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作.

**实现这个目标的关键之处,是设计一个抽象的组建类,让它可以代表组合对象和叶子对象.**这样一来客户端就不用区分到底是组合对象还是叶子对象了,只需要全部当成组件对象进行统一操作就可以了

2. 对象树

通常组合模式会组合出树形结构来,组成这个属性结构所使用的多个组件对象,就自然的形成了对象树

这也意味着凡是可以使用对象树来描述或操作的功能,都可以考虑使用组合模式,比如读取XML文件,或是对语句进行语法解析等。

3. 组合模式中的递归

组合模式中的递归,是对象本身的递归,是对象的组合方式,是从设计上来讲的,在设计上称作递归关联,是对象关联关系的一种

4. Component中是否应该实现一个Component列表

大多数情况下,一个Compoiste对象会持有子节点的集合.有些人会想,那么能不能把这个子节点集合定义到Component中去呢?因为在Component中还声明了一些操作子节点的方法,这样一来,大部分的工作就可以在Component中完成了。

事实上,这种方法是不太好的,因为在父类来存放子类的实例对象,对于Composite节点来说没有什么,它本来就需要存放子节点,但是对于叶子节点来说,就会导致空间的浪费,因为叶节点本身不需要子节点。

因此只有当组合结构中叶子对象数目较少的时候,才值得使用这种方法。

最大化Component定义

前面讲到了组合模式的目的是:让客户端不再区分操作的是组合对象还是叶子对象,而是以一种统一的方式来操作。

由于要统一两种对象的操作,所以Component里面的方法也主要是两种对象对外方法的和,换句话说,组件里面既有叶子对象需要的方法,也有组合对象需要的方法

其实这种实现是与类的设计原则相冲突的,类的设计有这样的原则:**一个父类应该只定义那些对它的子类有意义的操作。**但是看看上面的实现就知道,Component中的有些方法对于叶子对象是没有意义的。那么怎么解决这一冲突呢?

常见的做法是在Component里面为对某些子对象没有意义的方法,提供默认的实现,或是默认抛出不支持该功能的例外。这样一来,如果子对象需要这个功能,那就覆盖实现它,如果不需要,那就不用管了,使用父类的默认实现就可以了。

从另一个层面来说,如果把叶子对象看成是一个特殊的Composite对象,也就是没有子节点的组合对象而已。这样看来,对于Component而言,子对象就全部看作是组合对象,因此定义的所有方法都是有意义的了。

安全性和透明性

根据前面的讲述,在组合模式中,把组件对象分成了两种,一种是可以包含子组件的Composite对象,一种是不能包含其它组件对象的叶子对象。

Composite对象就像是一个容器,可以包含其它的Composite对象或叶子对象。当然有了容器,就要能对这个容器进行维护,需要向里面添加对象,并能够从容器里面获取对象,还有能从容器中删除对象,也就是说需要管理子组件对象。

这就产生了一个很重要的问题:到底在组合模式的类层次结构中,在哪一些类里面定义这些管理子组件的操作,到底应该在Component中声明这些操作,还是在Composite中声明这些操作?

这就需要仔细思考,在不同的实现中,进行安全性和透明性的权衡选择。

这里所说的安全性是指:从客户使用组合模式上看是否更安全。如果是安全的,那么不会有发生误操作的可能,能访问的方法都是被支持的功能。

这里所说的透明性是指:从客户使用组合模式上,是否需要区分到底是组合对象还是叶子对象。如果是透明的,那就是不再区分,对于客户而言,都是组件对象,具体的类型对于客户而言是透明的,是客户无需要关心的。

1. 透明性的实现

如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无需关心具体的组件类型,这种实现方式就是透明性的实现。

但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的,比如:增加、删除子组件对象。而客户不知道这些区别,对客户是透明的,因此客户可能会对叶子对象调用这种增加或删除子组件的方法,这样的操作是不安全的。

组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供缺省的实现,如果是有子对象不支持的功能,缺省的实现可以是抛出一个例外,来表示不支持这个功能

2. 安全性的实现

如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。

但是这样一来,客户端在使用的时候,就必须区分到底使用的是Composite对象,还是叶子对象,不同对象的功能是不一样的。也就是说,这种实现方式,对客户而言就不是透明的了

3. 两种实现方式的选择

对于组合模式而言,在安全性和透明性上,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性

而且对于安全性的实现,需要区分是组合对象还是叶子对象,但是有的时候,你需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的

对于这种情况的处理方法是在Component里面定义一个getComposite的方法,用来判断是组合对象还是叶子对象,如果是组合对象,就返回组合对象,如果是叶子对象,就返回null,这样就可以先判断,然后再强制转换。因此在使用组合模式的时候,建议多用透明性的实现方式,而少用安全性的实现方式

转载于:https://juejin.im/post/5d5fa5ddf265da03b94ffccf

结构型模式之组合模式相关推荐

  1. 结构型设计模式之组合模式

    结构型设计模式之组合模式 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创 ...

  2. (结构型)12、组合模式(部分即整体)

    组合模式 组合模式在定义类的时候就算是组合了,几乎无处不在,组合模式可以组合单一对象,也可以组合不同对象 在设计模式的总则中有一句话:"少用继承,多用组合.聚合的方式实现解耦" 单 ...

  3. 图解Java设计模式学习笔记——结构型模式(适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式、代理模式)

    一.适配器模式(类适配器.对象适配器.接口适配器) 1.现实生活中的例子 泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了国内的电器了. 2.基本介绍 适配器模式(Ad ...

  4. 设计模式之结构型模式:适配器模式、桥接模式、组合模式、装饰器模式、代理模式、

    文章目录 什么是结构型模式 适配模式 适配器的数据结构 适配器的实现 缺省适配器 适配器优缺点 适配器模式的使用环境 桥接模式 桥接模式数据结构 桥接模式的实现 桥接模式和适配器模式的联用 桥接模式的 ...

  5. 6-设计模式之结构型模式(桥接模式、外观模式、组合模式、享元模式)

    设计模式之结构型模式二(桥接模式.外观模式.组合模式.享元模式) 5.4 桥接模式 5.4.1 概述 5.4.2 结构 5.4.3 案例 5.4.4 使用场景 5.5 外观模式 5.5.1 概述 5. ...

  6. Java设计模式之结构型:享元模式

    一.什么是享元模式: 享元模式通过共享技术有效地支持细粒度.状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量.比如说一个文本系 ...

  7. Java设计模式之结构型:装饰器模式

    一.什么是装饰器模式: 当需要对类的功能进行拓展时,一般可以使用继承,但如果需要拓展的功能种类很繁多,那势必会生成很多子类,增加系统的复杂性,并且使用继承实现功能拓展时,我们必须能够预见这些拓展功能, ...

  8. 组合模式——透明组合模式,安全组合模式

    组合模式 概述 叶子节点进行相关的操作. 可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象. 但是由于容器对象和叶子对象在功能上面的区别,使得我们 ...

  9. 软件系统设计-7-适配器模式和组合模式

    1. 适配器模式 1.1. 模式动机 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式. 通常情况下,客户端可以通过目标类的接口访问它所提供的服务.有时,现有的类可以满足客户类的功能需 ...

  10. 迭代器模式和组合模式混用

    迭代器模式和组合模式混用 前言 园子里说设计模式的文章算得上是海量了,所以本篇文章所用到的迭代器设计模式和组合模式不提供原理解析,有兴趣的朋友可以到一些前辈的设计模式文章上学学,很多很有意思的.在He ...

最新文章

  1. 深入理解JVM读书笔记--内存管理
  2. 第十五届全国大学生智能汽车竞赛华北赛区比赛
  3. 阿里九峰:云计算开启的基础设施新时代
  4. python qq邮箱 群发
  5. FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别
  6. mysql 5.7优化不求人_《MySQL 5.7优化不求人》直播精彩互动
  7. C++Primer Plus (第六版)阅读笔记 + 源码分析【第四章:复合类型】
  8. 美团 iOS 工程 zsource 命令背后的那些事儿
  9. pythondistutils安装_python – 与distutils / pip一起安装Bash完成
  10. CoreLocation框架--监测方向/地磁传感器
  11. VC/MFC中的CComboBox控件使用详解
  12. uni-app开发微信小程序上传提示以下文件没有被打包上传
  13. opencv用python进行物体识别_教你用Python+opencv来识别物体的轮廓并标识显示
  14. if - else 案例.py
  15. ACM-水题 Demacia of the Ancients
  16. java抽象类的属性_JAVA 抽象类
  17. Windows 使用 ssh 命令行 通过密钥连接到 云服务器
  18. 2019银保监计算机类真题,2019中国银保监会招聘考试全真模拟卷(计算机类)
  19. html网站背景雪花飘落效果代码,超酷html5雪花飘落特效源码
  20. Java程序设计(二)

热门文章

  1. 10通信端口感叹号_开源企业级微信小程序实时通信聊天室技术架构演练
  2. 六石管理学:使用排除法解决问题
  3. MAC编译的JDK执行出错: [libjvm.dylib+0x482a49] PerfDataManager::destroy()+0xab
  4. No package ‘polkit-gobject-1‘ found
  5. 2021-07-25梦笔记
  6. LINUX下载编译FreeType
  7. 大型太阳能电池板在卫星上的使用,目前没有查到
  8. DEBUG模式下,视频丢包严重;RELEASE就好了
  9. python获取文件名中两条下划线之间的部分_Python 中的特殊双下划线方法
  10. php个人资料表单显示,php-如何显示用户从表单构建器中选择的带...