0%

组合模式

组合模式(Composite Pattern):构建 “部分 - 整体” 的树形结构

组合模式是结构型设计模式的一种,核心思想是将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,使客户端对单个对象(叶子节点)和组合对象(容器节点)的使用具有一致性。这种模式就像文件系统中的 “文件夹 - 文件” 结构,无论是单个文件还是文件夹(包含多个文件),都可以用统一的方式操作。

组合模式的核心结构

组合模式

组合模式通过三个核心角色构建树形结构,实现对 “部分” 和 “整体” 的统一处理:

抽象组件(Component)

  • 定义所有组件(叶子节点和组合节点)的公共接口,声明了添加、移除子组件、获取子组件及核心业务方法。
  • 示例:FileSystemNode(文件系统节点接口,定义getName()getSize()等方法)。

叶子节点(Leaf)

  • 表示树形结构中的最小单元(不可再包含子组件),实现抽象组件的接口,但不支持子组件操作(如添加、移除)。
  • 示例:File(文件,不可包含子节点)。

组合节点(Composite)

  • 表示树形结构中的容器(可包含子组件,子组件可以是叶子节点或其他组合节点),实现抽象组件的接口,负责管理子组件(添加、移除、遍历)。
  • 示例:Folder(文件夹,可包含文件或子文件夹)。

代码实现示例

以 “文件系统” 为例,展示组合模式的实现:文件夹(Composite)可包含文件(Leaf)或子文件夹,客户端可统一获取大小、打印结构等。

1. 抽象组件(Component)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 抽象组件:文件系统节点
public abstract class FileSystemNode {
protected String name; // 节点名称

public FileSystemNode(String name) {
this.name = name;
}

// 核心方法:获取节点大小
public abstract long getSize();

// 获取节点名称
public String getName() {
return name;
}

// 添加子节点(默认不支持,由Composite重写)
public void addChild(FileSystemNode child) {
throw new UnsupportedOperationException("不支持添加子节点");
}

// 移除子节点(默认不支持)
public void removeChild(FileSystemNode child) {
throw new UnsupportedOperationException("不支持移除子节点");
}

// 获取子节点(默认不支持)
public List<FileSystemNode> getChildren() {
throw new UnsupportedOperationException("不支持获取子节点");
}
}

2. 叶子节点(Leaf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 叶子节点:文件
public class File extends FileSystemNode {
private long size; // 文件大小(字节)

public File(String name, long size) {
super(name);
this.size = size;
}

// 实现获取大小的方法(文件大小即自身大小)
@Override
public long getSize() {
return size;
}
}

3. 组合节点(Composite)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 组合节点:文件夹
public class Folder extends FileSystemNode {
private List<FileSystemNode> children = new ArrayList<>(); // 子节点列表

public Folder(String name) {
super(name);
}

// 实现获取大小的方法(文件夹大小=所有子节点大小之和)
@Override
public long getSize() {
long totalSize = 0;
for (FileSystemNode child : children) {
totalSize += child.getSize(); // 递归计算子节点大小
}
return totalSize;
}

// 重写添加子节点方法
@Override
public void addChild(FileSystemNode child) {
children.add(child);
}

// 重写移除子节点方法
@Override
public void removeChild(FileSystemNode child) {
children.remove(child);
}

// 重写获取子节点方法
@Override
public List<FileSystemNode> getChildren() {
return new ArrayList<>(children); // 返回不可修改的副本
}
}

4. 客户端使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class CompositeDemo {
public static void main(String[] args) {
// 创建叶子节点(文件)
FileSystemNode file1 = new File("文档1.txt", 1024);
FileSystemNode file2 = new File("图片1.png", 2048);

// 创建组合节点(文件夹)
Folder folder1 = new Folder("工作资料");
folder1.addChild(file1);
folder1.addChild(file2);

// 创建嵌套组合节点(子文件夹)
FileSystemNode file3 = new File("视频1.mp4", 4096);
Folder folder2 = new Folder("娱乐资料");
folder2.addChild(file3);
folder1.addChild(folder2); // 将子文件夹添加到父文件夹

// 统一操作:获取大小(无论是文件还是文件夹)
System.out.println(folder1.getName() + "总大小:" + folder1.getSize() + "字节"); // 1024+2048+4096=7168
System.out.println(file1.getName() + "大小:" + file1.getSize() + "字节"); // 1024

// 统一操作:打印结构
printStructure(folder1, 0);
}

// 递归打印树形结构(统一处理叶子和组合节点)
private static void printStructure(FileSystemNode node, int depth) {
// 打印缩进
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
// 打印节点名称
System.out.println(indent + "- " + node.getName() + "(" + node.getSize() + "字节)");

// 若为组合节点,递归打印子节点
if (node instanceof Folder) {
for (FileSystemNode child : ((Folder) node).getChildren()) {
printStructure(child, depth + 1);
}
}
}
}
输出结果
1
2
3
4
5
6
7
工作资料总大小:7168字节
文档1.txt大小:1024字节
- 工作资料(7168字节)
- 文档1.txt(1024字节)
- 图片1.png(2048字节)
- 娱乐资料(4096字节)
- 视频1.mp4(4096字节)

组合模式的核心优势

  1. 统一处理 “部分” 和 “整体”
    客户端无需区分叶子节点和组合节点,可通过相同的接口操作单个对象或整个树形结构(如获取大小、打印结构),简化了客户端代码。
  2. 灵活构建树形结构
    可动态添加或移除子节点,轻松构建复杂的层次结构(如嵌套文件夹),符合开闭原则(扩展时无需修改现有代码)。
  3. 清晰的层次关系
    通过树形结构直观表示 “部分 - 整体” 关系,使系统设计更符合现实世界的结构(如组织架构、文件系统)。

适用场景

  1. 存在 “部分 - 整体” 层次关系
    当系统中的对象可自然地组织成树形结构,且需要统一操作单个对象和组合对象时(如:
    • 文件系统:文件夹(整体)包含文件(部分)。
    • 组织架构:部门(整体)包含员工(部分)或子部门。
    • 菜单系统:主菜单(整体)包含子菜单(部分)或菜单项。
  2. 客户端需统一操作所有对象
    客户端希望忽略 “部分” 和 “整体” 的差异,用相同的代码处理不同层级的对象(如计算总大小、遍历所有节点)。
  3. 需动态维护层次结构
    系统需要支持动态添加、移除节点,或调整节点的层级关系(如动态生成菜单、调整部门结构)。

优缺点分析

优点

  • 简化客户端代码:统一接口使客户端无需区分叶子和组合节点,减少条件判断。
  • 易于扩展:新增叶子或组合节点时,只需实现抽象组件接口,无需修改现有代码。
  • 清晰的层次表示:树形结构直观反映对象间的 “部分 - 整体” 关系,便于理解和维护。

缺点

  • 设计复杂度增加:需要定义抽象组件并处理子节点操作,对简单场景可能显得冗余。
  • 限制类型灵活性:若需严格区分叶子和组合节点的类型(如禁止某些操作),可能需要额外的类型检查。

经典应用案例

  1. GUI 组件框架
    如 Swing 中的JComponentJPanel(组合节点,可包含其他组件)和JButton(叶子节点)都继承自JComponent,客户端可统一添加到容器或设置属性。
  2. XML/HTML 文档解析
    文档的 DOM 树结构:Element(组合节点,可包含子元素)和TextNode(叶子节点)通过统一接口操作,便于遍历或修改文档结构。
  3. 组织架构管理系统
    部门(Department)包含员工(Employee)或子部门,可统一计算部门总人数、薪资总额等。

总结

组合模式通过抽象组件、叶子节点和组合节点的配合,完美实现了 “部分 - 整体” 层次结构的统一处理。其核心价值在于 “一致性”—— 让客户端用相同的方式操作单个对象和复杂组合对象,同时支持动态构建和维护树形结构。在需要表示层次关系且需统一操作的场景中,组合模式是简化设计、提高灵活性的理想选择

欢迎关注我的其它发布渠道