作者 薛之谦chj 转载请注明出处
我的知乎:https://zhuanlan.zhihu.com/c_1229107265379897344
内容简介:
定义:
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示 “部分-整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite Pattern) 也称为 整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。
组合模式 一般用来描述 整体 与 部分 的关系,它将对象组织到树形结构中,最顶层的节点称为 根节点,根节点下面可以包含 树枝节点 和 叶子节点,树枝节点下面又可以包含 树枝节点 和 叶子节点。如下图所示:
由上图可以看出,其实 根节点 和 树枝节点 本质上是同一种数据类型(蓝色圆圈),可以作为容器使用;而 叶子节点 与 树枝节点 在语义上不属于同一种类型,但是在 组合模式 中,会把 树枝节点 和 叶子节点 认为是同一种数据类型(用同一接口定义),让它们具备一致行为。这样,在 组合模式 中,整个树形结构中的对象都是同一种类型,带来的一个好处就是客户无需辨别 树枝节点 还是 叶子节点,而是可以直接进行操作,给客户使用带来极大的便利。
组合模式 核心:借助同一接口,使叶子节点和树枝节点的操作具备一致性。
主要解决
当子系统与其内各个对象层次呈现树形结构时,可以使用 组合模式 让该子系统内各个对象层次的行为操作具备一致性。客户端使用该子系统内任意一个层次对象时,无须进行区分,直接使用通用操作即可,为客户端的使用带来了便捷。
注:如果树形结构系统不使用 组合模式 进行架构,那么按照正常的思维逻辑,对该系统进行职责分析,按上文树形结构图所示,该系统具备两种对象层次类型:树枝节点和叶子节点。那么我们就需要构造两种对应的类型,然后由于树枝节点具备容器功能,因此树枝节点类内部需维护多个集合存储其他对象层次(eg:List<Composite>,List<Leaf>),如果当前系统对象层次更复杂时,那么树枝节点内就又要增加对应的层次集合,这对树枝节点的构建带来了巨大的复杂性,臃肿性以及不可扩展性。同时客户端访问该系统层次时,还需进行层次区分,这样才能使用对应的行为,给客户端的使用也带来了巨大的复杂性。而如果使用 组合模式 构建该系统,由于 组合模式 抽取了系统各个层次的共性行为,具体层次只需按需实现所需行为即可,这样子系统各个层次就都属于同一种类型,所以树枝节点只需维护一个集合(List<Component>)即可存储系统所有层次内容,并且客户端也无需区分该系统各个层次对象,对内系统架构简洁优雅,对外接口精简易用。
优缺点
优点
组合模式 屏蔽了对象系统的层次差异性(树节点和叶子节点为不同类型),将客户代码与复杂的容器对象解耦,使得客户端可以忽略层次间的差异,使用一致的行为控制不同层次。
在 组合模式 可以很方便地增加 树枝节点 和 叶子节点 对象,并对现有类库无侵入,符合 开闭原则;
缺点
如果类系统(树形结构)过于庞大,虽然对不同层次都提供一致性操作,但客户端仍需花费时间理清类之间的层次关系;
组合模式 在具体实现上违背了设计模式 接口隔离原则 或 依赖倒置原则;
使用场景
系统对象层次具备整体和部分,呈树形结构,且要求具备统一行为(如树形菜单,操作系统目录结构,公司组织架构等);
模式讲解
组合模式 主要包含三种角色:
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性;
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构;
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位;
组合模式 在代码具体实现上,有两种不同的方式:
透明模式:把组合(树节点)使用的方法放到统一行为(Component)中,让不同层次(树节点,叶子节点)的结构都具备一致行为;其 UML 类图如下所示:
安全模式:统一行为(Component)只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中;其 UML 类图如下所示:
安全组合模式 把系统各层次公有的行为定义在 Component 中,把组合(树节点)特有的行为(管理子类增加,删除等)放到自身(Composite)中。这样做的好处是接口定义职责清晰,符合设计模式 单一职责原则 和 接口隔离原则;缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式 依赖倒置原则。
问:透明组合模式 和 安全组合模式 都有各自的优点和缺点,那么我们应该优先选择哪一种呢?
答:既然 组合模式 会被分为两种实现,那么肯定是不同的场合某一种会更加适合,也即具体情况具体分析。透明组合模式 将公共接口封装到抽象根节点(Component)中,那么系统所有节点就具备一致行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用 透明组合模式 也许会更好(代价:为剩下少数层次节点引入不需要的方法);而如果当系统各个层次差异性行为较多或者树节点层次相对稳定(健壮)时,采用 安全组合模式
注:设计模式的出现并不是说我们要写的代码一定要遵循设计模式所要求的方方面面,这是不现实同时也是不可能的。设计模式的出现,其实只是强调好的代码所具备的一些特征(六大设计原则),这些特征对于项目开发是具备积极效应的,但不是说我们每实现一个类就一定要全部满足设计模式的要求,如果真的存在完全满足设计模式的要求,反而可能存在过度设计的嫌疑。同时,23种设计模式,其实都是严格依循设计模式六大原则进行设计,只是不同的模式在不同的场景中会更加适用。设计模式的理解应该重于意而不是形,真正编码时,经常使用的是某种设计模式的变形体,真正切合项目的模式才是正确的模式。
具体实现:
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
直接来看代码:
组合模式的应用
1. 何时使用
想表达“部分-整体”层次结构(树形结构)时
希望用户忽略组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象
2. 方法
树枝和叶子实现统一接口,树枝内部组合该接口
3. 优点
高层模块调用简单。一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,高层模块不必关心自己处理的是单个对象还是整个组合结构。
节点自由增加
4. 缺点
使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒转原则
5. 使用场景
维护和展示部分-整体关系的场景(如树形菜单、文件和文件夹管理)
从一个整体中能够独立出部分模块或功能的场景
将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
6. 应用实例
Swing中,Button、Checkbox等组件都是树叶,而Container容器是树枝
文本编辑时,可以单个字编辑,也可以整段编辑,还可以全文编辑
文件复制时,可以一个一个文件复制,也可以整个文件夹复制