组合形式

组合形式是一种结构型规划形式, 你可以运用它将目标组合成树状结构, 并且能像运用独立目标一样运用它们。

遇到的问题

假如运用的中心模型能用树状结构表明, 在运用中运用组合形式才有价值。

例如, 你有两类目标: 产品盒子 。 一个盒子中可以包括多个 产品或许几个较小的 盒子 。 这些小 盒子中同样可以包括一些 产品或更小的 盒子 , 以此类推。

假定你希望在这些类的基础上开发一个定购系统。 订单中可以包括无包装的简略产品, 也可以包括装满产品的盒子…… 以及其他盒子。 此刻你会怎么核算每张订单的总价格呢?

2023 跟我一起学设计模式:组合模式

订单中可能包括各种产品, 这些产品放置在盒子中, 然后又被放入一层又一层更大的盒子中。 整个结构看上去像是一棵倒过来的树。

你可以测验直接核算: 翻开一切盒子, 找到每件产品, 然后核算总价。 这在实在世界中或许可行, 但在程序中, 你并不能简略地运用循环句子来完结该作业。 你有必要事先知道一切 产品盒子的类别, 一切盒子的嵌套层数以及其他繁杂的细节信息。 因而, 直接核算极不方便, 乃至完全不可行。

解决方案

组合形式建议运用一个通用接口来与 产品盒子进行交互, 并且在该接口中声明一个核算总价的办法。

那么办法该怎么规划呢? 关于一个产品, 该办法直接回来其价格; 关于一个盒子, 该办法遍历盒子中的一切项目, 问询每个项目的价格, 然后回来该盒子的总价格。 假如其间某个项目是小一号的盒子, 那么当时盒子也会遍历其间的一切项目, 以此类推, 直到核算出一切内部组成部分的价格。 你乃至可以在盒子的终究价格中添加额定费用, 作为该盒子的包装费用。

2023 跟我一起学设计模式:组合模式

组合形式以递归办法处理目标树中的一切项目

该办法的最大长处在于你无需了解构成树状结构的目标的详细类。 你也无需了解目标是简略的产品仍是杂乱的盒子。 你只需调用通用接口以相同的办法对其进行处理即可。 当你调用该办法后, 目标会将恳求沿着树结构传递下去。

实在世界类比

2023 跟我一起学设计模式:组合模式

部队结构的比如。

大部分国家的戎行都选用层次结构管理。 每支部队包括几个师, 师由旅构成, 旅由团构成, 团可以持续划分为排。 最终, 每个排由一小队实实在在的战士组成。 军事指令由最高层下达, 经过每个层级传递, 直到每位战士都知道自己应该遵守的指令。

组合形式结构

2023 跟我一起学设计模式:组合模式

  1. 组件 (Component) 接口描绘了树中简略项目和杂乱项目所共有的操作。

  2. 叶节点 (Leaf) 是树的根本结构, 它不包括子项目。

    一般情况下, 叶节点终究会完结大部分的实际作业, 由于它们无法将作业指派给其他部分。

  3. 容器 (Container)——又叫 “组合 (Composite)”——是包括叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的详细类, 它只经过通用的组件接口与其子项目交互。

    容器接收到恳求后会将作业分配给自己的子项目, 处理中间成果, 然后将终究成果回来给客户端。

  4. 客户端 (Client) 经过组件接口与一切项目交互。 因而, 客户端能以相同办法与树状结构中的简略或杂乱项目交互。

伪代码

在本例中, 咱们将凭借组合形式协助你在图形编辑器中实现一系列的几许图形。

2023 跟我一起学设计模式:组合模式

几许形状编辑器示例。

组合图形CompoundGraphic是一个容器, 它可以由多个包括容器在内的子图形构成。 组合图形与简略图形具有相同的办法。 可是, 组合图形本身并不完结详细作业, 而是将恳求递归地传递给自己的子项目, 然后 “汇总” 成果。

经过一切图形类所共有的接口, 客户端代码可以与一切图形互动。 因而, 客户端不知道与其交互的是简略图形仍是组合图形。 客户端可以与非常杂乱的目标结构进行交互, 而无需与组成该结构的实体类紧密耦合。

组合形式适合运用场景

假如你需求实现树状目标结构, 可以运用组合形式。

组合形式为你供给了两种共享公共接口的根本元素类型: 简略叶节点和杂乱容器。 容器中可以包括叶节点和其他容器。 这使得你可以构建树状嵌套递归目标结构。

假如你希望客户端代码以相同办法处理简略和杂乱元素, 可以运用该形式。

组合形式中界说的一切元素共用同一个接口。 在这一接口的协助下, 客户端不用介意其所运用的目标的详细类。

实现办法

  1. 保证运用的中心模型可以以树状结构表明。 测验将其分解为简略元素和容器。 记住, 容器有必要可以同时包括简略元素和其他容器。

  2. 声明组件接口及其一系列办法, 这些办法对简略和杂乱元素都有意义。

  3. 创立一个叶节点类表明简略元素。 程序中可以有多个不同的叶节点类。

  4. 创立一个容器类表明杂乱元素。 在该类中, 创立一个数组成员变量来存储关于其子元素的引证。 该数组有必要可以同时保存叶节点和容器, 因而请保证将其声明为组合接口类型。

    实现组件接口办法时, 记住容器应该将大部分作业交给其子元从来完结。

  5. 最终, 在容器中界说添加和删除子元素的办法。

    记住, 这些操作可在组件接口中声明。 这将会违反接口阻隔原则, 由于叶节点类中的这些办法为空。 可是, 这可以让客户端无差别地拜访一切元素, 即使是组成树状结构的元素。

组合形式优缺点

  • 你可以利用多态和递归机制更方便地运用杂乱树结构。

  • 开闭原则。 无需更改现有代码, 你就可以在运用中添加新元素, 使其成为目标树的一部分。

  • 关于功能差异较大的类, 供给公共接口或许会有困难。 在特定情况下, 你需求过度一般化组件接口, 使其变得令人难以了解。

代码示例

  • Golang 组合形式讲解和代码示例 – 掘金 ()