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

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