摘要生成中...
AI 摘要
Hunyuan-lite

image.png

组合模式 Composite Design Pattern

Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.——GoF
将一组对象组织成树形结构,以表示一种「部分 - 整体」的层次结构。组合让客户端可以统一单个对象和组合对象的处理逻辑。

使用组合模式使容器与内容具有一致性,又称为多个和单个的一致性,将多个对象结合在一起,当作一个对象进行处理,创造出递归结构。

Composite:混合物、复合物

image.png

image.png

登场角色:

  • Leaf(树叶):表示「内容」的角色。在该角色中不能放入其他对象。
  • Composite(复合物):表示容器的角色。可以在其中放入 Leaf 角色和 Composite 角色。
  • Component:使 Leaf 角色和 Composite 角色具有一致性的角色。Composite 角色是 Leaf 角色和 Composite 角色的父类。
  • Client:使用组合模式的角色。

组合模式以递归方式处理对象树中的所有项目。该方式的最大优点在于无需了解构成树状结构的对象的具体类。也无需了解对象是简单的产品还是复杂的盒子。只需调用通用接口以相同的方式对其进行处理即可。当调用该方法后,对象会将请求沿着树结构传递下去。

image.png

应用场景:

  • 实现树状对象结构
  • 客户端代码以相同方式处理简单和复杂元素
优点 缺点
利用多态和递归机制更方便地使用复杂树结构。 对于功能差异较大的类,提供公共接口或许会有困难。 在特定情况下,需要过度一般化组件接口,使其变得令人难以理解。
开闭原则。无需更改现有代码,就可以在应用中添加新元素,使其成为对象树的一部分。

相关的设计模式:

  • 站内文章装饰器模式:组合模式的 UML 和装饰器模式很像,具体详看装饰器模式的介绍文章。
  • [[「用类来表现」的设计模式|命令模式]]:使用命令模式编写宏命令时使用了组合模式。
  • 站内文章迭代器模式:用迭代器遍历组合树。
  • 站内文章访问者模式:可以使用访问者模式访问组合模式中的递归结构。
  • 站内文章建造者模式:在创建复杂组合树时使用生成器模式,因为这可使其构造步骤以递归的方式运行。
  • 站内文章责任链模式:叶组件接收到请求后,可以将请求沿包含全体父组件的链一直传递至对象树的底部。
  • [[享元模式:共享实例|享元模式]]:实现组合树的共享叶结点以节省内存。

组合模式的应用

与其说组合模式是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。

在程序世界中,到处都存在递归结构。比如视窗系统中,一个窗口可以含有一个子窗口;文章列表中,各列表之间可以相互嵌套;计算机命令的宏命令的实现。通常来说,树结构的数据结构都适用于组合模式。

一个文件系统的例子

image.png

image.png

对于 Composite 中有一些有别于 Leaf 的方法,比如上面文件系统中的 add 方法,这些方法应当如何处理?

  • 方法 1:定义在 Entry 类中,默认报错。
    • add 方法定义在 Entry 类中,让其报错,这是上图 UML 中的示例做法。能使用 add 方法的只有 Directory 类,它会重写 add 方法,根据需求实现其处理。File 类会继承 Entry 类的 add 方法,虽然也可以调用它的 add 方法,不过会抛出异常。
  • 方法 2:定义在 Entry 类中,但什么都不做。
  • 方法 3:声明在 Entry 类中,但不实现。
    • Entry 类中声明 add 抽象方法。如果子类需要 add 方法就根据需求实现该方法,如果不需要 add 方法,则可以简单地报错。该方法的优点是所有子类必须都实现 add 方法,不需要 add 方法时的处理也可以交给子类自己去做决定。不过,使用这种实现方法时,在 File 一方中也必须定义本来完全不需要的 add(有时还包括 removegetchild)方法。
  • 方法 4:只定义在 Directory 类中。
    • 因为只有 Directory 类可以使用 add 方法,所以可以不在 Entry 类中定义 add 方法,而是只将其定义在 Directory 类中。不过,使用这种方法时,如果要向 Entry 类型的变量(实际保存的是 Directory 类的实例)中 add 时,需要先将它们一个一个地类型转换(cast)为 Directory 类型。

本文参考