组合模式(Composite Pattern)
组合模式是结构型设计模式的核心之一,其核心目标是 “将对象组合成树形结构,以表示‘部分 - 整体’的层次关系”,并让客户端对单个对象(叶子节点)和组合对象(容器节点)的访问具有一致性。它如同文件系统中的 “文件夹与文件”—— 文件夹(容器)可包含子文件夹(子容器)和文件(叶子),客户端无论是操作单个文件还是整个文件夹,都可通过统一接口(如 “删除”“复制”)完成。
一、组合模式核心概念(通用)
在展开细节前,先明确组合模式的通用定义、意图等核心要素:
1. 定义
将对象组合成树形结构以表示 “部分 - 整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
2. 意图
- 统一 “部分” 与 “整体” 的访问接口:客户端无需区分单个对象(叶子)和组合对象(容器),可通过相同接口操作。
- 简化客户端代码:避免客户端使用大量
if-else判断 “当前对象是叶子还是容器”,降低代码复杂度。 - 动态构建树形结构:支持在运行时动态添加 / 删除节点(叶子或容器),灵活扩展层次结构。
3. 通用核心组件
组合模式的核心是通过 “抽象构件” 统一接口,再由 “叶子构件” 和 “容器构件” 实现具体逻辑,三者构成基本结构:
| 角色名称 | 职责描述 |
|---|---|
| Component(抽象构件) | 定义所有构件(叶子 / 容器)的统一接口,包含客户端所需的核心方法(如 operation()),同时可能定义管理子构件的方法(如 add()/remove(),默认抛出异常或空实现)。 |
| Leaf(叶子构件) | 树形结构的 “叶子节点”,表示 “部分”,无子构件。实现 Component 接口的核心方法,但不实现子构件管理方法(或直接抛出 “不支持该操作” 的异常)。 |
| Composite(容器构件) | 树形结构的 “非叶子节点”,表示 “整体”,可包含子构件(Leaf 或其他 Composite)。实现 Component 接口的核心方法(通常需遍历子构件并调用其 operation()),同时实现子构件管理方法(add()/remove()/getChild())。 |
二、组合模式详细解析(以 “文件系统” 为例)
以日常使用的 “文件系统” 为场景展开 ——File(文件,叶子)和 Folder(文件夹,容器)都属于 “文件系统构件”,客户端可通过统一接口(如 showInfo() 显示信息、delete() 删除)操作,无需区分是文件还是文件夹。
1. 结构
- Component(抽象构件):
FileSystemComponent,定义showInfo()(显示构件信息)、delete()(删除构件),以及子构件管理方法add()/remove()/getChild()。 - Leaf(叶子构件):
File,无子女,实现showInfo()(显示文件名和大小)和delete()(删除文件),子构件管理方法抛出异常。 - Composite(容器构件):
Folder,可包含File或其他Folder,实现showInfo()(显示文件夹名 + 子构件数量)和delete()(递归删除所有子构件 + 自身),同时实现子构件管理方法。
2. 类图(Mermaid)

classDiagram
class FileSystemComponent {
<<Abstract>>
+showInfo(): void // 核心方法:显示构件信息
+delete(): void // 核心方法:删除构件
+add(component: FileSystemComponent): void // 管理子构件:添加
+remove(component: FileSystemComponent): void // 管理子构件:删除
+getChild(index: int): FileSystemComponent // 管理子构件:获取子构件
}
class File {
-name: String
-size: long // 文件大小(字节)
+File(name: String, size: long)
+showInfo(): void
+delete(): void
+add(component: FileSystemComponent): void // 抛出UnsupportedOperationException
+remove(component: FileSystemComponent): void // 抛出UnsupportedOperationException
+getChild(index: int): FileSystemComponent // 抛出UnsupportedOperationException
}
class Folder {
-name: String
-children: List~FileSystemComponent~ // 存储子构件(File/Folder)
+Folder(name: String)
+showInfo(): void
+delete(): void
+add(component: FileSystemComponent): void
+remove(component: FileSystemComponent): void
+getChild(index: int): FileSystemComponent
}
%% 叶子构件继承抽象构件
FileSystemComponent <|-- File
%% 容器构件继承抽象构件
FileSystemComponent <|-- Folder
%% 容器可包含0~N个抽象构件(组合关系)
Folder "1" *-- "0..*" FileSystemComponent
3. 时序图(Mermaid)
以 “客户端删除包含文件的文件夹” 为例,展示组合模式的调用流程:
sequenceDiagram
participant Client(客户端)
participant Folder(容器构件:我的文档)
participant File(叶子构件:简历.pdf)
participant SubFolder(容器构件:照片)
participant SubFile(叶子构件:旅行.jpg)
%% 1. 客户端调用文件夹的delete()方法
Client->>Folder: delete()(删除“我的文档”)
%% 2. 文件夹先删除所有子构件
Folder->>File: delete()(删除“简历.pdf”)
File-->>Folder: 确认删除(返回成功)
Folder->>SubFolder: delete()(删除“照片”子文件夹)
%% 3. 子文件夹删除自身的子构件
SubFolder->>SubFile: delete()(删除“旅行.jpg”)
SubFile-->>SubFolder: 确认删除(返回成功)
%% 4. 子文件夹删除自身
SubFolder-->>Folder: 确认删除(返回成功)
%% 5. 根文件夹删除自身
Folder-->>Client: 确认删除(返回成功)
4. 优点
- 客户端一致性访问:无需区分叶子和容器,通过统一接口操作,简化客户端代码(如删除 “文件” 和 “文件夹” 都调用
delete())。 - 灵活扩展树形结构:可在运行时动态添加 / 删除节点(如给文件夹新增子文件、删除子文件夹),符合 “开闭原则”。
- 清晰表示层次关系:通过树形结构直观体现 “部分 - 整体” 关系(如文件夹包含子文件夹和文件),逻辑清晰。
- 简化递归操作:容器构件的方法(如
delete())可通过递归调用子构件的方法,自动处理所有子节点,无需客户端干预。
5. 缺点
- 限制子构件类型困难:若需限制容器只能包含特定类型的子构件(如 “照片文件夹只能包含图片文件”),需在
add()中增加判断逻辑,破坏了 “开闭原则”(新增类型需修改容器代码)。 - 抽象构件设计复杂:抽象构件需同时定义 “核心业务方法” 和 “子构件管理方法”,叶子构件需覆盖子构件管理方法并抛出异常,增加了抽象层的设计复杂度。
- 性能开销(递归场景):若树形结构过深(如嵌套 100 层文件夹),容器构件的递归操作(如
delete())可能导致栈溢出或性能下降。
三、Java 代码实现(文件系统示例)
1. 抽象构件(FileSystemComponent)
定义所有构件的统一接口,子构件管理方法默认抛出异常(叶子构件可直接继承,容器构件需重写):
import java.util.List;
// 抽象构件:文件系统构件
public abstract class FileSystemComponent {
// 核心方法:显示构件信息(文件名/文件夹名+详情)
public abstract void showInfo();
// 核心方法:删除构件
public abstract void delete();
// 子构件管理:添加(默认不支持,抛出异常)
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException("当前构件不支持添加操作");
}
// 子构件管理:删除(默认不支持,抛出异常)
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException("当前构件不支持删除操作");
}
// 子构件管理:获取子构件(默认不支持,抛出异常)
public FileSystemComponent getChild(int index) {
throw new UnsupportedOperationException("当前构件不支持获取子构件操作");
}
}
2. 叶子构件(File)
无子女,实现核心方法,子构件管理方法直接继承抽象类的 “抛出异常” 逻辑:
// 叶子构件:文件(无子女)
public class File extends FileSystemComponent {
private final String name; // 文件名
private final long size; // 文件大小(字节)
public File(String name, long size) {
this.name = name;
this.size = size;
}
// 显示文件信息:文件名+大小
@Override
public void showInfo() {
System.out.printf("文件:%s,大小:%d KB(%d 字节)%n",
name, size / 1024, size);
}
// 删除文件:模拟删除逻辑
@Override
public void delete() {
System.out.printf("已删除文件:%s%n", name);
}
}
3. 容器构件(Folder)
可包含子构件,重写所有方法,子构件管理通过 List 实现,核心方法通过递归调用子构件逻辑:
import java.util.ArrayList;
import java.util.List;
// 容器构件:文件夹(可包含File或Folder)
public class Folder extends FileSystemComponent {
private final String name;
private final List<FileSystemComponent> children; // 存储子构件
public Folder(String name) {
this.name = name;
this.children = new ArrayList<>();
}
// 显示文件夹信息:文件夹名+子构件数量
@Override
public void showInfo() {
System.out.printf("文件夹:%s,包含 %d 个构件%n",
name, children.size());
// 递归显示所有子构件信息
for (FileSystemComponent component : children) {
component.showInfo();
}
}
// 删除文件夹:先递归删除所有子构件,再删除自身
@Override
public void delete() {
// 1. 递归删除子构件
for (FileSystemComponent component : children) {
component.delete();
}
// 2. 删除自身
System.out.printf("已删除文件夹:%s%n", name);
}
// 新增子构件(File或Folder)
@Override
public void add(FileSystemComponent component) {
children.add(component);
System.out.printf("已向文件夹【%s】添加:%s%n",
name, component.getClass().getSimpleName());
}
// 移除子构件
@Override
public void remove(FileSystemComponent component) {
if (children.remove(component)) {
System.out.printf("已从文件夹【%s】移除:%s%n",
name, component.getClass().getSimpleName());
} else {
System.out.printf("文件夹【%s】中不存在该构件%n", name);
}
}
// 获取指定索引的子构件
@Override
public FileSystemComponent getChild(int index) {
if (index >= 0 && index < children.size()) {
return children.get(index);
}
throw new IndexOutOfBoundsException("子构件索引越界");
}
}
4. 客户端(Client)
通过统一接口操作叶子和容器,无需区分类型:
// 客户端:操作文件系统构件
public class Client {
public static void main(String[] args) {
// 1. 创建叶子构件(文件)
FileSystemComponent resumeFile = new File("简历.pdf", 102400); // 100KB
FileSystemComponent travelPhoto = new File("旅行.jpg", 204800); // 200KB
FileSystemComponent notesFile = new File("笔记.txt", 2048); // 2KB
// 2. 创建容器构件(文件夹)
FileSystemComponent photoFolder = new Folder("照片");
FileSystemComponent docFolder = new Folder("我的文档");
// 3. 构建树形结构:我的文档 -> 照片文件夹 + 简历文件;照片文件夹 -> 旅行照片
photoFolder.add(travelPhoto); // 照片文件夹添加旅行照片
docFolder.add(resumeFile); // 我的文档添加简历
docFolder.add(photoFolder); // 我的文档添加照片文件夹
docFolder.add(notesFile); // 我的文档添加笔记
// 4. 统一接口操作:显示“我的文档”的所有信息(递归显示子构件)
System.out.println("=== 显示我的文档结构 ===");
docFolder.showInfo();
// 5. 统一接口操作:删除“我的文档”(递归删除所有子构件+自身)
System.out.println("\n=== 删除我的文档 ===");
docFolder.delete();
}
}
5. 输出结果
已向文件夹【照片】添加:File
已向文件夹【我的文档】添加:File
已向文件夹【我的文档】添加:Folder
已向文件夹【我的文档】添加:File
=== 显示我的文档结构 ===
文件夹:我的文档,包含 3 个构件
文件:简历.pdf,大小:100 KB(102400 字节)
文件夹:照片,包含 1 个构件
文件:旅行.jpg,大小:200 KB(204800 字节)
文件:笔记.txt,大小:2 KB(2048 字节)
=== 删除我的文档 ===
已删除文件:简历.pdf
已删除文件:旅行.jpg
已删除文件夹:照片
已删除文件:笔记.txt
已删除文件夹:我的文档
四、适用环境
组合模式适用于以下场景,核心判断标准是 “存在‘部分 - 整体’层次关系,且需统一访问接口”:
- 需表示 “部分 - 整体” 层次结构:如文件系统(文件夹 - 文件)、组织架构(公司 - 部门 - 员工)、UI 组件树(布局 - 按钮 - 文本框)。
- 客户端需统一访问叶子和容器:客户端无需区分 “单个对象” 和 “组合对象”,希望通过相同接口操作(如删除文件和删除文件夹)。
- 需动态扩展层次结构:需在运行时灵活添加 / 删除节点(如给文件夹新增子文件、给部门新增员工),且不影响客户端代码。
- 需递归处理树形结构:如 “统计文件夹总大小”“遍历所有 UI 组件” 等场景,容器构件可通过递归自动处理所有子节点。
五、模式分析
1. 核心本质
组合模式的本质是 “统一接口 + 树形结构 + 递归处理”:
- 统一接口:通过抽象构件消除 “叶子” 和 “容器” 的差异,客户端无需感知类型。
- 树形结构:通过容器构件的 “子构件列表” 构建层次关系,直观体现 “部分 - 整体”。
- 递归处理:容器构件的方法(如
delete())通过递归调用子构件的方法,自动覆盖所有子节点,简化逻辑。
2. 与其他模式的区别
- 与适配器模式的区别:适配器模式解决 “接口不兼容”,不改变对象结构;组合模式解决 “部分 - 整体层次访问”,核心是构建树形结构。
- 与装饰器模式的区别:装饰器模式不改变对象结构,仅为对象动态添加功能;组合模式改变对象结构(构建树形),核心是统一 “部分” 和 “整体” 的访问。
- 与迭代器模式的区别:迭代器模式用于遍历集合(包括组合模式的子构件列表);组合模式是构建集合结构的模式,迭代器是遍历结构的工具。
六、模式扩展
组合模式可根据需求扩展出不同变体,核心是调整 “抽象构件” 和 “容器构件” 的设计:
1. 透明组合模式(Transparent Composite)
- 特点:抽象构件
Component定义所有子构件管理方法(add()/remove()),叶子构件需覆盖这些方法并抛出异常。 - 优点:客户端访问更透明(无需区分叶子和容器,所有构件都有相同方法)。
- 缺点:叶子构件的子构件管理方法无实际意义,可能导致客户端误调用(如给文件调用
add())。 - 适用场景:客户端完全无需区分叶子和容器,且能保证不调用叶子的子构件管理方法。
2. 安全组合模式(Safe Composite)
- 特点:抽象构件
Component仅定义核心业务方法(operation()),子构件管理方法(add()/remove())仅在容器构件Composite中定义。 - 优点:避免客户端误调用叶子的子构件管理方法,安全性更高。
- 缺点:客户端需区分叶子和容器(调用
add()前需判断是否为Composite),破坏了访问的一致性。 - 适用场景:需严格限制子构件管理操作,避免客户端误操作的场景。
3. 带权重的组合模式
- 特点:为每个构件添加 “权重” 属性(如文件大小、员工薪资),容器构件的权重为所有子构件权重之和(通过递归计算)。
- 应用场景:需统计 “整体” 的聚合属性(如文件夹总大小、部门总薪资)。
4. 带缓存的组合模式
- 特点:容器构件缓存子构件的聚合结果(如总大小),避免每次调用都递归计算,提升性能。
- 适用场景:树形结构庞大且聚合结果不频繁变化(如统计大型文件夹的总大小)。
七、模式应用(通用 + Android)
1. 通用领域应用
- 文件系统:Windows/macOS 的文件系统(Folder/File),通过组合模式实现 “文件夹包含文件 / 子文件夹”,统一操作接口(删除、复制)。
- 组织架构系统:公司管理系统(Company -> Department -> Employee),Employee 是叶子,Department 是容器,统一计算 “部门总人数”“公司总薪资”。
- 图形绘制系统:如 Photoshop 的图层系统(GroupLayer -> Layer/SubGroup),GroupLayer 是容器,Layer 是叶子,统一执行 “显示图层”“隐藏图层” 操作。
- XML/JSON 解析:解析后的 DOM 树(Node -> Element/Text),Element 是容器(可包含子 Node),Text 是叶子,统一遍历和修改节点。
2. Android 中的应用
Android 中大量场景依赖 “UI 组件树” 和 “层次结构”,组合模式是核心设计思路:
(1)View 与 ViewGroup(UI 组件树)
- 角色对应:
View:抽象构件(定义draw()/onTouchEvent()等核心方法,子构件管理方法默认不支持);- 具体 View(如
TextView/Button):叶子构件(无子女,实现draw()显示自身); ViewGroup:容器构件(继承View,实现addView()/removeView()管理子 View,draw()递归绘制所有子 View)。
- 核心逻辑:客户端(如 Activity)操作
View或ViewGroup时,无需区分类型(如调用setVisibility()隐藏按钮或隐藏整个布局),通过统一接口View实现。
// 示例:操作 View(叶子)和 ViewGroup(容器)
TextView textView = new TextView(this); // 叶子构件
LinearLayout linearLayout = new LinearLayout(this); // 容器构件
// 统一接口操作:设置可见性(无需区分类型)
textView.setVisibility(View.GONE);
linearLayout.setVisibility(View.GONE);
// 容器构件管理子 View
linearLayout.addView(textView); // 添加叶子构件
linearLayout.removeView(textView); // 移除叶子构件
(2)Menu 与 SubMenu(菜单系统)
- 角色对应:
Menu:抽象构件(定义add()/removeItem()等方法);MenuItem:叶子构件(单个菜单项,无子女);SubMenu:容器构件(继承Menu,可包含MenuItem或子SubMenu,实现多级菜单)。
- 核心逻辑:客户端创建菜单时,无需区分 “菜单项” 和 “子菜单”,通过
Menu接口统一添加(如menu.addSubMenu("设置")添加子菜单,menu.add("退出")添加菜单项)。
(3)RecyclerView 的 Item 嵌套(复杂列表)
- 场景:
- RecyclerView 的 Item 包含 “子列表”(如 “订单 Item” 包含 “商品子 Item”),此时:
- 外层
RecyclerView.Adapter管理 “订单 Item”(容器构件); - 内层
RecyclerView.Adapter管理 “商品子 Item”(叶子构件); - 客户端通过统一的
Adapter.notifyDataSetChanged()刷新整个列表(无论外层还是内层)。
八、总结
组合模式是处理 “部分 - 整体” 层次关系的核心模式,其核心价值在于 “统一接口 + 灵活扩展”:
- 核心优势:客户端无需区分叶子和容器,通过统一接口操作,简化代码;树形结构支持动态扩展,符合 “开闭原则”;递归处理自动覆盖所有子节点,减少重复逻辑。
- 适用场景:需表示层次结构(如文件系统、UI 组件树)、需统一访问接口(如操作菜单 / 子菜单)的场景。
- 实践建议:
- 优先使用 “透明组合模式”(抽象构件定义所有方法),除非有严格的安全需求(如避免客户端误操作);
- 若树形结构过深,需注意递归性能(如添加缓存或限制层级);
- 结合迭代器模式遍历子构件(如 Android 的
ViewGroup遍历子 View 时使用getChildAt()),提升代码灵活性。
组合模式不仅是一种设计模式,更是一种 “层次化思维”—— 通过树形结构将复杂的 “整体” 拆解为可管理的 “部分”,同时通过统一接口让客户端无需关注内部结构,是构建复杂系统的重要工具。