urlname
type
Post
password
SyncToConfluence
category
业务技能
date
Apr 2, 2025
slug
7af003cb-2e9f-4784-8599-2b388f107489
icon
Button
catalog
summary
tags
编程基础
设计模式
专业能力
代码质量
软件工程
学习笔记
cover
Status
BusyTime
Status 1
status
Published

什么是组合模式(Composite Pattern)

组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次结构。它使得客户端统一地处理单个对象(叶子节点)和对象的组合(容器节点)。这属于结构型模式。
想象一下 Android 的视图系统:
  • 有一个基础的 View 类(可以看作是 Component)。
  • 有像 TextView, ImageView, Button 这样的单个视图(叶子节点 Leaf),它们直接绘制内容,不能包含其他视图。
  • 有像 LinearLayout, FrameLayout, ConstraintLayout 这样的视图组(容器节点 Composite),它们本身也是 View,但它们的主要职责是包含并管理其他的 View(可以是叶子节点,也可以是其他视图组)。
组合模式的关键在于,无论你拿到的是一个 TextView 还是一个 LinearLayout,你都可以调用像 draw(), measure(), requestLayout() 这样的方法。客户端代码不需要关心它操作的是一个简单的叶子节点还是一个复杂的容器节点。
组合模式的核心在于提供一个统一的接口来处理树形结构中的所有节点(叶子和容器)。

核心思想/步骤

  1. 定义组件接口/抽象类 (Component): 定义一个接口或抽象类,声明了叶子节点 (Leaf) 和容器节点 (Composite) 共有的操作。例如,operation(), add(Component), remove(Component), getChild(index)。
  1. 创建叶子节点 (Leaf): 实现 Component 接口/继承抽象类。叶子节点代表层次结构中的末端对象,它没有子节点。因此,对于管理子节点的操作(如 add, remove, getChild),叶子节点通常会提供空实现、抛出异常或返回 null。它主要实现 Component 接口中定义的核心操作(如 operation())。
  1. 创建容器节点 (Composite): 实现 Component 接口/继承抽象类。容器节点代表层次结构中有子节点的节点。它内部持有一个 Component 对象的集合(列表、映射等)来存储其子节点。它实现了 Component 接口定义的核心操作,其实现通常会委托/递归调用其子节点上的相应操作。同时,它也实现了管理子节点的方法(add, remove, getChild)。
  1. 客户端交互: 客户端代码通过 Component 接口与对象进行交互,无需区分它是叶子节点还是容器节点。这使得客户端代码更加简单和通用。
关于 add/remove/getChild 的位置 (设计权衡):
  • 放在 Component 接口(透明方式): 客户端可以统一处理所有 Component,但叶子节点需要对这些方法提供无效实现(可能违反接口隔离原则)。这是更常见的做法。
  • 只放在 Composite 类(安全方式): 接口更清晰,叶子节点更纯粹。但客户端在需要管理子节点时,必须先判断对象是否为 Composite 类型,牺牲了一部分透明性。

优点

  1. 统一处理 (Uniformity): 客户端代码可以一致地处理单个对象和组合对象,简化了客户端逻辑。
  1. 表示层次结构 (Hierarchical Structures): 非常适合表示具有“部分-整体”关系的树形结构。
  1. 易于添加新组件 (Extensibility): 可以很容易地增加新的 Leaf 或 Composite 类,只要它们实现了 Component 接口,客户端代码无需修改(符合开闭原则)。
  1. 代码更简洁: 避免了客户端代码中大量的 if/else 来判断对象类型。

可能的缺点

  1. 接口设计可能过于通用 (Overly General Interface): 如果将管理子节点的方法放在 Component 接口中(透明方式),那么 Leaf 类也必须实现这些方法(通常是空实现或抛异常),这可能使得接口不够纯粹。
  1. 运行时类型检查 (Potential Runtime Checks): 虽然目标是统一处理,但在某些特定场景下,客户端可能仍然需要区分 Leaf 和 Composite(例如,只对 Composite 执行 add 操作),这可能需要进行类型检查或转换,略微破坏了透明性。
  1. 难以限制组合类型: 有时可能希望限制一个 Composite 只能包含特定类型的 Component,标准模式本身不直接支持这一点,需要额外逻辑。

使用场景

  1. 当你需要表示对象的部分-整体层次结构时(树形结构)。
  1. 当你希望客户端忽略组合对象与单个对象的差异时。
  1. GUI 系统: 视图 (Leaf) 和视图容器 (Composite) 的组织,如 Android View/ViewGroup, Java Swing/AWT 组件。这是最经典的应用场景。
  1. 文件系统: 文件 (Leaf) 和目录/文件夹 (Composite)。
  1. 组织结构: 员工 (Leaf) 和部门 (Composite)。
  1. 菜单系统: 菜单项 (Leaf) 和子菜单 (Composite)。
  1. 图形绘制: 基本图形(点、线、圆 - Leaf)和组合图形(由多个基本图形组成 - Composite)。
  1. Android 中的场景:
      • View / ViewGroup: 这是 Android UI 框架的核心,完美体现了组合模式。View 是 Component,具体控件是 Leaf,布局容器是 Composite。测量 (onMeasure)、布局 (onLayout)、绘制 (onDraw) 等操作都体现了递归调用的思想。
      • Menu / MenuItem / SubMenu: Android 中的菜单系统也使用了组合模式。MenuItem 可以是叶子,SubMenu 既是 Menu 也是 MenuItem(可以包含其他项),扮演 Composite 角色。
      • 建模树状数据: 如果你需要处理如组织架构、文件目录结构等具有层级关系的数据,组合模式是自然的选择。

UML 图

下面是基于一个简单的“图形绘制”示例的组合模式 UML 图:
  • Graphic (Component): 定义了所有图形(简单或复合)的通用接口 draw(),以及管理子项的方法(采用透明方式,默认抛异常)。
  • Dot, Line (Leaf): 实现了 Graphic。它们负责绘制自己,但不能包含其他图形,所以 add/remove/getChild 会抛出异常。
  • CompoundGraphic (Composite): 实现了 Graphic。它持有一个 children 列表(List<Graphic>)。它的 draw() 方法会遍历 children 并调用每个子项的 draw()。它也实现了 add, remove, getChild 来管理子项。
  • Client: 通过 Graphic 接口与图形对象交互,无论是简单的 Dot 还是复杂的 CompoundGraphic,都可以调用 draw()。

代码示例

在这个例子中:
  • Graphic 是统一的接口。
  • Dot 和 Line 是叶子节点,只实现了 draw。
  • CompoundGraphic 是容器节点,它持有 Graphic 对象的列表,并实现了 draw(通过递归调用子项)以及 add, remove, getChild。
  • 客户端代码 (main 和 drawPicture 函数) 通过 Graphic 接口与所有对象交互,无需关心具体是叶子还是容器,清晰地展示了组合模式的统一性。
  • 我们还演示了尝试在叶子节点上调用 add 会按预期抛出异常。

总结

  • 组合模式通过定义统一的接口,使得客户端能够一致地处理树形结构中的单个对象(Leaf)和组合对象(Composite)
  • 它非常适合用来表示和操作具有“部分-整体”关系的层次结构
  • 在 Android 开发中,View 和 ViewGroup 的关系是组合模式最经典的、也是我们每天都在使用的例子。
💡
记住它的核心:树形结构,统一接口,叶子与容器一视同仁
 
设计模式——结构型-桥接模式设计模式——行为型-中介者模式
Loading...