组合模式(Composite)大白话讲解
一句话概括
就像文件夹和文件的关系:文件夹可以包含文件,也可以包含其他文件夹,但你对它们的操作是统一的

现实生活比喻
场景1:公司组织架构
- 员工:普通员工(叶子节点)
- 经理:可以管理多个员工(组合节点)
- CEO:可以管理多个经理(组合节点)
- 操作:计算总工资、发布通知等操作对所有人都一样
场景2:菜单系统
- 菜单项:具体的菜(叶子节点)
- 子菜单:包含多个菜单项(组合节点)
- 主菜单:包含多个子菜单(组合节点)
- 操作:显示菜单、计算总价等操作对各级都一样
完整代码示例
场景:文件系统
java
/**
* 组合模式 - 文件系统示例
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== 构建文件系统 ===");
// 创建文件(叶子节点)
FileSystemComponent file1 = new File("简历.doc", 200);
FileSystemComponent file2 = new File("照片.jpg", 1500);
FileSystemComponent file3 = new File("报告.pdf", 500);
FileSystemComponent file4 = new File("代码.java", 100);
FileSystemComponent file5 = new File("数据.txt", 50);
// 创建文件夹(组合节点)
FileSystemComponent workFolder = new Folder("工作资料");
FileSystemComponent personalFolder = new Folder("个人文件");
FileSystemComponent projectFolder = new Folder("项目文档");
FileSystemComponent rootFolder = new Folder("我的电脑");
// 构建树形结构
workFolder.add(file1);
workFolder.add(file3);
personalFolder.add(file2);
personalFolder.add(file4);
projectFolder.add(file5);
workFolder.add(projectFolder); // 文件夹中可以包含文件夹
rootFolder.add(workFolder);
rootFolder.add(personalFolder);
// 统一操作 - 显示整个文件系统
System.out.println("\n=== 文件系统结构 ===");
rootFolder.display();
// 统一操作 - 计算总大小
System.out.println("\n=== 统计信息 ===");
System.out.println("工作资料大小: " + workFolder.getSize() + "KB");
System.out.println("个人文件大小: " + personalFolder.getSize() + "KB");
System.out.println("整个电脑大小: " + rootFolder.getSize() + "KB");
// 统一操作 - 搜索文件
System.out.println("\n=== 搜索文件 ===");
searchFile(rootFolder, "代码.java");
searchFile(rootFolder, "不存在的文件.xyz");
}
// 统一的搜索方法,对文件和文件夹都适用
public static void searchFile(FileSystemComponent component, String fileName) {
if (component.search(fileName)) {
System.out.println("找到文件: " + fileName);
} else {
System.out.println("未找到文件: " + fileName);
}
}
}
/**
* 抽象组件 - 定义文件和文件夹的共同接口
*/
interface FileSystemComponent {
void display(); // 显示
int getSize(); // 获取大小
boolean search(String fileName); // 搜索文件
void add(FileSystemComponent component); // 添加(对文件无效)
void remove(FileSystemComponent component); // 删除(对文件无效)
}
/**
* 叶子节点 - 文件
*/
class File implements FileSystemComponent {
private String name;
private int size; // 文件大小,单位KB
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public void display() {
System.out.println("文件: " + name + " (" + size + "KB)");
}
@Override
public int getSize() {
return size;
}
@Override
public boolean search(String fileName) {
return this.name.equals(fileName);
}
@Override
public void add(FileSystemComponent component) {
// 文件不能添加子组件,抛出异常或忽略
throw new UnsupportedOperationException("文件不支持添加操作");
}
@Override
public void remove(FileSystemComponent component) {
// 文件不能删除子组件,抛出异常或忽略
throw new UnsupportedOperationException("文件不支持删除操作");
}
}
/**
* 组合节点 - 文件夹
*/
class Folder implements FileSystemComponent {
private String name;
private List<FileSystemComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("文件夹: " + name);
// 递归显示所有子组件
for (FileSystemComponent child : children) {
System.out.print(" ");
child.display();
}
}
@Override
public int getSize() {
int totalSize = 0;
// 递归计算所有子组件的大小
for (FileSystemComponent child : children) {
totalSize += child.getSize();
}
return totalSize;
}
@Override
public boolean search(String fileName) {
// 先检查当前文件夹是否匹配
if (this.name.equals(fileName)) {
return true;
}
// 递归搜索所有子组件
for (FileSystemComponent child : children) {
if (child.search(fileName)) {
return true;
}
}
return false;
}
@Override
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void remove(FileSystemComponent component) {
children.remove(component);
}
}
运行结果
=== 构建文件系统 ===
=== 文件系统结构 ===
文件夹: 我的电脑
文件夹: 工作资料
文件: 简历.doc (200KB)
文件: 报告.pdf (500KB)
文件夹: 项目文档
文件: 数据.txt (50KB)
文件夹: 个人文件
文件: 照片.jpg (1500KB)
文件: 代码.java (100KB)
=== 统计信息 ===
工作资料大小: 750KB
个人文件大小: 1600KB
整个电脑大小: 2350KB
=== 搜索文件 ===
找到文件: 代码.java
未找到文件: 不存在的文件.xyz
更实用的例子:商品分类系统
java
/**
* 电商平台商品分类系统
*/
public class ProductCategoryExample {
public static void main(String[] args) {
// 创建商品(叶子节点)
ProductComponent phone1 = new Product("iPhone 15", 6999);
ProductComponent phone2 = new Product("小米14", 3999);
ProductComponent laptop1 = new Product("MacBook Pro", 12999);
ProductComponent laptop2 = new Product("ThinkPad", 7999);
ProductComponent tshirt = new Product("T恤", 99);
ProductComponent jeans = new Product("牛仔裤", 199);
// 创建分类(组合节点)
ProductComponent electronics = new Category("电子产品");
ProductComponent clothing = new Category("服装");
ProductComponent phones = new Category("手机");
ProductComponent laptops = new Category("笔记本电脑");
ProductComponent root = new Category("所有商品");
// 构建分类树
phones.add(phone1);
phones.add(phone2);
laptops.add(laptop1);
laptops.add(laptop2);
electronics.add(phones);
electronics.add(laptops);
clothing.add(tshirt);
clothing.add(jeans);
root.add(electronics);
root.add(clothing);
// 统一操作
System.out.println("=== 商品分类结构 ===");
root.display();
System.out.println("\n=== 价格统计 ===");
System.out.println("电子产品总价值: " + electronics.getPrice() + "元");
System.out.println("手机总价值: " + phones.getPrice() + "元");
System.out.println("所有商品总价值: " + root.getPrice() + "元");
System.out.println("\n=== 搜索商品 ===");
searchProduct(root, "iPhone 15");
searchProduct(root, "MacBook Pro");
}
public static void searchProduct(ProductComponent component, String productName) {
if (component.search(productName)) {
System.out.println("找到商品: " + productName);
}
}
}
/**
* 商品组件接口
*/
interface ProductComponent {
void display();
double getPrice();
boolean search(String productName);
void add(ProductComponent component);
void remove(ProductComponent component);
}
/**
* 具体商品(叶子节点)
*/
class Product implements ProductComponent {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public void display() {
System.out.println("商品: " + name + " - " + price + "元");
}
@Override
public double getPrice() {
return price;
}
@Override
public boolean search(String productName) {
return this.name.equals(productName);
}
@Override
public void add(ProductComponent component) {
throw new UnsupportedOperationException("商品不能添加子项");
}
@Override
public void remove(ProductComponent component) {
throw new UnsupportedOperationException("商品不能删除子项");
}
}
/**
* 商品分类(组合节点)
*/
class Category implements ProductComponent {
private String name;
private List<ProductComponent> children = new ArrayList<>();
public Category(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("分类: " + name);
for (ProductComponent child : children) {
System.out.print(" ");
child.display();
}
}
@Override
public double getPrice() {
double total = 0;
for (ProductComponent child : children) {
total += child.getPrice();
}
return total;
}
@Override
public boolean search(String productName) {
for (ProductComponent child : children) {
if (child.search(productName)) {
return true;
}
}
return false;
}
@Override
public void add(ProductComponent component) {
children.add(component);
}
@Override
public void remove(ProductComponent component) {
children.remove(component);
}
}
组合模式的核心结构
Component(抽象组件)
/ \
/ \
Leaf(叶子) Composite(组合)
|
|
包含多个Component
关键特征:
- 统一接口:叶子和组合实现相同的接口
- 递归结构:组合可以包含其他组合,形成树形结构
- 透明性:客户端无需区分叶子和组合
透明式 vs 安全式
透明式(推荐)
java
// 在抽象接口中定义所有方法(包括add/remove)
interface Component {
void operation();
void add(Component c); // 叶子节点抛出异常
void remove(Component c); // 叶子节点抛出异常
}
优点 :客户端统一对待所有组件
缺点:叶子节点需要实现不需要的方法
安全式
java
// 只在组合接口中定义管理子组件的方法
interface Component {
void operation();
}
interface Composite extends Component {
void add(Component c);
void remove(Component c);
}
优点 :避免叶子节点实现不需要的方法
缺点:客户端需要判断组件类型
适用场景
✅ 适合用组合模式的场景:
-
树形结构数据
java// 组织架构、文件系统、菜单系统 // 任何需要表示"部分-整体"层次结构的场景 -
统一处理需求
java// 需要对整个树执行相同操作 root.calculate(); // 计算整个树 branch.calculate(); // 计算分支 leaf.calculate(); // 计算叶子 -
递归操作
java// 深度优先搜索、递归统计等 int total = root.getTotal();
❌ 不适合的场景:
- 组件差异很大:如果叶子和组合的行为完全不同
- 性能敏感:递归操作可能影响性能
- 简单结构:如果结构很简单,不需要这么复杂的设计
优缺点
优点:
- 简化客户端代码:客户端可以一致地处理叶子和组合
- 易于扩展:新增组件类型很容易
- 符合开闭原则:可以灵活添加新的组件
缺点:
- 设计复杂:需要仔细设计接口
- 类型安全问题:透明式需要在运行时检测操作是否支持
- 性能考虑:递归操作可能成为性能瓶颈
总结
组合模式就是:
- 树形结构:表示部分-整体的层次关系
- 统一操作:对叶子节点和组合节点的操作方式一致
- 递归处理:组合节点委托子节点完成操作
核心口诀:
部分整体层次树,
统一操作好维护。
叶子组合一个样,
递归处理真舒服!
就像现实中的:
- 🏢 组织架构:员工和部门都可以计算工资总额
- 📁 文件系统:文件和文件夹都可以计算大小
- 🎮 游戏场景:简单物体和复杂场景都可以渲染
- 📊 UI组件:按钮和面板都可以刷新显示
记住:当你需要处理树形结构,并且希望对单个对象和组合对象使用统一的操作时,使用组合模式!