设计模式之一致性_Composite组合模式_容器和内容的一致性(对象包含对象)
一、组合模式(Composite Pattern)
1、模式概述
组合模式 是一种 结构型设计模式,它将对象组合成 树形结构 以表示“部分-整体”的层次结构。组合模式使得用户对 单个对象(叶子) 和 组合对象(容器) 的使用具有一致性。
核心思想:“树形结构,统一接口 —— 无论是容器还是内容,操作方式一样。”
组合模式 = 树形结构 + 递归遍历
- 它不是“简单的包含”,而是“层级的嵌套”,让系统能够以统一的方式处理个体与群体。
2、使用场景
组合模式适用于以下情况:
- 你想表示 对象的部分-整体层次结构;
- 你希望用户 忽略组合对象与单个对象的不同,统一地使用它们;
- 需要构建 树形结构,且结构可能动态变化。
关键特征:
-
递归结构:树枝节点可以包含其他树枝或叶子;
-
透明性:客户端无需关心当前操作的是容器还是原子对象。
典型应用场景:
- 文件系统(文件夹包含文件夹或文件);
- 组织架构(部门包含部门或员工);
- 菜单系统(菜单包含子菜单或菜单项)。
在实际开发中:
Java AWT/Swing的Container和Component是组合模式的经典应用;JSON/XML解析器常使用组合模式构建文档对象模型(DOM);- 电商系统的商品分类(一级分类、二级分类、商品)也常采用此模式。
3、示例程序:文件系统树
- Component 是统一入口;
- Leaf 和 Composite 实现同一接口,但行为不同;
- Composite 负责递归调用子节点的操作。
| 角色 | 职责 | 示例 |
|---|---|---|
| Component(抽象构件) | 定义所有节点(叶子和容器)的公共接口 | FileSystemNode |
| Leaf(叶子) | 表示不可再分的原子对象,没有子节点 | File |
| Composite(容器) | 表示可包含子节点的复合对象,管理子节点集合 | Directory |
| Client(客户端) | 通过 Component 接口操作树形结构 | Main |
┌───────────────────┐
│ FileSystemNode │<──────────┐
│-------------------│ │
│ + name: String │ │
│-------------------│ │
│ + display(depth) │ │
│ + getSize(): long │ │
└─────────▲─────────┘ │
│ │
┌─────────────────┴─────────────────┐ │
│ │ │
┌───────▼───────┐ ┌──────────▼───────────┐
│ File │ │ Directory │
│---------------│ │----------------------│
│ - size: long │ │ - children: List<> │
│---------------│ │----------------------│
│ + display() │ │ + add(node) │
│ + getSize() │ │ + remove(node) │
└───────────────┘ │ + getChildren() │
│ + display() │
│ + getSize() │
└──────────────────────┘
1)抽象构件 FileSystemNode
/**
* 抽象构件(Component)
* 定义文件系统中所有节点的公共行为
*/
public abstract class FileSystemNode {
protected String name;
public FileSystemNode(String name) {
this.name = name;
}
// 获取名称
public String getName() {
return name;
}
// 显示节点信息(由子类实现)
public abstract void display(int depth);
// 获取大小(字节),叶子返回实际大小,容器返回子节点总和
public abstract long getSize();
}
2)叶子节点 File
/**
* 叶子(Leaf)
* 表示一个具体的文件,不可再分
*/
public class File extends FileSystemNode {
private long size; // 文件大小(字节)
public File(String name, long size) {
super(name);
this.size = size;
}
@Override
public void display(int depth) {
System.out.println(" ".repeat(depth) + "📄 " + name + " (" + size + " bytes)");
}
@Override
public long getSize() {
return size;
}
}
3)容器节点 Directory
/**
* 容器(Composite)
* 表示一个目录,可包含文件或其他目录
*/
import java.util.ArrayList;
import java.util.List;
public class Directory extends FileSystemNode {
private final List<FileSystemNode> children = new ArrayList<>();
public Directory(String name) {
super(name);
}
// 添加子节点
public void add(FileSystemNode node) {
children.add(node);
}
// 移除子节点
public void remove(FileSystemNode node) {
children.remove(node);
}
// 获取子节点列表(可选)
public List<FileSystemNode> getChildren() {
return new ArrayList<>(children); // 返回副本,保证封装性
}
@Override
public void display(int depth) {
// 打印当前目录
System.out.println(" ".repeat(depth) + "📁 " + name + "/");
// 递归打印所有子节点
for (FileSystemNode child : children) {
child.display(depth + 1);
}
}
@Override
public long getSize() {
// 目录大小 = 所有子节点大小之和
return children.stream().mapToLong(FileSystemNode::getSize).sum();
}
}
4)客户端测试 Main
- 客户端只通过
FileSystemNode接口操作; display()和getSize()在File和Directory中行为不同,但调用方式一致;- 树结构可无限嵌套(如
docs下还可加子目录)。
/**
* 客户端(Client)
* 构建文件系统树并操作
*/
public class Main {
public static void main(String[] args) {
// 创建根目录
Directory root = new Directory("root");
// 创建子目录
Directory docs = new Directory("docs");
Directory pics = new Directory("pics");
// 创建文件(叶子)
File readme = new File("README.md", 1024);
File report = new File("report.pdf", 2048);
File photo1 = new File("vacation.jpg", 4096);
File photo2 = new File("family.png", 3072);
// 组装树形结构
docs.add(readme);
docs.add(report);
pics.add(photo1);
pics.add(photo2);
root.add(docs);
root.add(pics);
// 统一操作:显示整个文件系统
System.out.println("=== 文件系统结构 ===");
root.display(0);
// 统一操作:获取总大小
System.out.println("\n总大小: " + root.getSize() + " bytes");
}
}
输出:
=== 文件系统结构 ===
📁 root/
📁 docs/
📄 README.md (1024 bytes)
📄 report.pdf (2048 bytes)
📁 pics/
📄 vacation.jpg (4096 bytes)
📄 family.png (3072 bytes)
总大小: 10240 bytes
3、FQA
1)模式优点
- 定义清晰的层次结构:树形结构直观明了;
- 统一调用接口:客户端无需区分容器与叶子(如果使用透明式);
- 扩展性强:增加新的树枝或叶子非常容易(符合开闭原则)。
2)注意事项
- 设计复杂度:如果业务逻辑简单(无层级关系),使用组合模式属于过度设计;
- 类型安全性:在透明式组合模式中,叶子节点的
add/remove方法可能没有意义(需要抛出异常或空实现); - 遍历效率:树结构过深时,递归遍历可能导致栈溢出。


