23种设计模式23种设计模式
首页
介绍
  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式
  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 代理模式
  • 责任链模式
  • 命令模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者模式
  • 状态模式
  • 策略模式
  • 模板方法模式
  • 访问者模式
🚀 编程指南
首页
介绍
  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式
  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 代理模式
  • 责任链模式
  • 命令模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者模式
  • 状态模式
  • 策略模式
  • 模板方法模式
  • 访问者模式
🚀 编程指南
  • 结构型模式

    • 适配器模式 (Adapter)
    • 桥接模式 (Bridge)
    • 组合模式 (Composite)
    • 装饰器模式 (Decorator)
    • 外观模式 (Facade)
    • 享元模式 (Flyweight)
    • 代理模式 (Proxy)

组合模式 (Composite)

📖 通俗理解

想象你电脑里的文件系统:

  • 文件夹里可以放文件
  • 文件夹里也可以放文件夹
  • 打开一个文件夹,里面的结构可能和外面一样

这就是一个树形结构!无论是文件还是文件夹,你都可以对它们进行相同的操作(复制、删除、移动)。

组合模式就是:把对象组合成树形结构,让客户端对单个对象和组合对象的使用具有一致性。

🎯 解决什么问题?

场景:你有一个树形结构的数据,希望能够统一处理叶子节点和容器节点。

例子:

  • 文件系统(文件和文件夹)
  • 公司组织架构(员工和部门)
  • 菜单系统(菜单项和子菜单)

🌰 生活中的例子

  • 文件夹结构:文件夹可以包含文件和子文件夹
  • 公司组织:部门可以包含员工和子部门
  • 电商分类:分类可以包含商品和子分类
  • XML/HTML:标签可以包含文本和子标签

💻 Java 代码实现

场景:文件系统

第一步:定义抽象组件

/**
 * 抽象组件:文件系统节点
 */
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();
    
    // 以下方法对于叶子节点不适用,提供默认实现
    public void add(FileSystemNode node) {
        throw new UnsupportedOperationException("不支持添加操作");
    }
    
    public void remove(FileSystemNode node) {
        throw new UnsupportedOperationException("不支持删除操作");
    }
    
    public FileSystemNode getChild(int index) {
        throw new UnsupportedOperationException("不支持获取子节点操作");
    }
}

第二步:实现叶子节点(文件)

/**
 * 叶子节点:文件
 */
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) {
        // 根据深度打印缩进
        String indent = "  ".repeat(depth);
        System.out.println(indent + "📄 " + name + " (" + size + " bytes)");
    }
    
    @Override
    public long getSize() {
        return size;
    }
}

第三步:实现容器节点(文件夹)

import java.util.ArrayList;
import java.util.List;

/**
 * 容器节点:文件夹
 */
public class Folder extends FileSystemNode {
    
    private List<FileSystemNode> children = new ArrayList<>();
    
    public Folder(String name) {
        super(name);
    }
    
    @Override
    public void add(FileSystemNode node) {
        children.add(node);
    }
    
    @Override
    public void remove(FileSystemNode node) {
        children.remove(node);
    }
    
    @Override
    public FileSystemNode getChild(int index) {
        return children.get(index);
    }
    
    @Override
    public void display(int depth) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + "📁 " + name + "/");
        
        // 递归显示所有子节点
        for (FileSystemNode child : children) {
            child.display(depth + 1);
        }
    }
    
    @Override
    public long getSize() {
        long totalSize = 0;
        // 递归计算所有子节点的大小
        for (FileSystemNode child : children) {
            totalSize += child.getSize();
        }
        return totalSize;
    }
}

第四步:使用组合模式

public class Client {
    public static void main(String[] args) {
        // 创建根目录
        Folder root = new Folder("root");
        
        // 创建子目录
        Folder documents = new Folder("documents");
        Folder pictures = new Folder("pictures");
        Folder music = new Folder("music");
        
        // 添加文件到 documents
        documents.add(new File("简历.docx", 25600));
        documents.add(new File("报告.pdf", 102400));
        
        // 在 documents 下创建子目录
        Folder work = new Folder("work");
        work.add(new File("项目计划.xlsx", 51200));
        work.add(new File("会议纪要.docx", 12800));
        documents.add(work);
        
        // 添加文件到 pictures
        pictures.add(new File("风景.jpg", 2048000));
        pictures.add(new File("头像.png", 512000));
        
        // 添加文件到 music
        music.add(new File("歌曲1.mp3", 5120000));
        music.add(new File("歌曲2.mp3", 4800000));
        
        // 组装目录结构
        root.add(documents);
        root.add(pictures);
        root.add(music);
        root.add(new File("readme.txt", 1024));
        
        // 显示整个目录结构
        System.out.println("=== 目录结构 ===");
        root.display(0);
        
        // 计算总大小
        System.out.println("\n=== 目录大小 ===");
        System.out.println("root 总大小: " + root.getSize() + " bytes");
        System.out.println("documents 大小: " + documents.getSize() + " bytes");
        System.out.println("pictures 大小: " + pictures.getSize() + " bytes");
    }
}

输出:

=== 目录结构 ===
📁 root/
  📁 documents/
    📄 简历.docx (25600 bytes)
    📄 报告.pdf (102400 bytes)
    📁 work/
      📄 项目计划.xlsx (51200 bytes)
      📄 会议纪要.docx (12800 bytes)
  📁 pictures/
    📄 风景.jpg (2048000 bytes)
    📄 头像.png (512000 bytes)
  📁 music/
    📄 歌曲1.mp3 (5120000 bytes)
    📄 歌曲2.mp3 (4800000 bytes)
  📄 readme.txt (1024 bytes)

=== 目录大小 ===
root 总大小: 12673024 bytes
documents 大小: 192000 bytes
pictures 大小: 2560000 bytes

🔥 实战案例:公司组织架构

/**
 * 抽象组件:组织节点
 */
public abstract class OrganizationNode {
    
    protected String name;
    
    public OrganizationNode(String name) {
        this.name = name;
    }
    
    // 计算薪资
    public abstract double getSalary();
    
    // 显示组织结构
    public abstract void display(int depth);
    
    public void add(OrganizationNode node) {
        throw new UnsupportedOperationException();
    }
    
    public void remove(OrganizationNode node) {
        throw new UnsupportedOperationException();
    }
}

/**
 * 叶子节点:员工
 */
public class Employee extends OrganizationNode {
    
    private String position;
    private double salary;
    
    public Employee(String name, String position, double salary) {
        super(name);
        this.position = position;
        this.salary = salary;
    }
    
    @Override
    public double getSalary() {
        return salary;
    }
    
    @Override
    public void display(int depth) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + "👤 " + name + " - " + position + 
                          " (¥" + salary + ")");
    }
}

/**
 * 容器节点:部门
 */
public class Department extends OrganizationNode {
    
    private List<OrganizationNode> members = new ArrayList<>();
    
    public Department(String name) {
        super(name);
    }
    
    @Override
    public void add(OrganizationNode node) {
        members.add(node);
    }
    
    @Override
    public void remove(OrganizationNode node) {
        members.remove(node);
    }
    
    @Override
    public double getSalary() {
        double total = 0;
        for (OrganizationNode member : members) {
            total += member.getSalary();
        }
        return total;
    }
    
    @Override
    public void display(int depth) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + "🏢 " + name + " (总薪资: ¥" + getSalary() + ")");
        
        for (OrganizationNode member : members) {
            member.display(depth + 1);
        }
    }
}

使用方式:

public class CompanyDemo {
    public static void main(String[] args) {
        // 创建公司
        Department company = new Department("ABC科技有限公司");
        
        // 技术部
        Department techDept = new Department("技术部");
        techDept.add(new Employee("张三", "技术总监", 50000));
        
        // 技术部下的研发组
        Department devTeam = new Department("研发组");
        devTeam.add(new Employee("李四", "高级工程师", 30000));
        devTeam.add(new Employee("王五", "中级工程师", 20000));
        devTeam.add(new Employee("赵六", "初级工程师", 12000));
        techDept.add(devTeam);
        
        // 技术部下的测试组
        Department testTeam = new Department("测试组");
        testTeam.add(new Employee("钱七", "测试经理", 25000));
        testTeam.add(new Employee("孙八", "测试工程师", 15000));
        techDept.add(testTeam);
        
        // 人事部
        Department hrDept = new Department("人事部");
        hrDept.add(new Employee("周九", "人事经理", 25000));
        hrDept.add(new Employee("吴十", "招聘专员", 10000));
        
        // 组装公司
        company.add(new Employee("老板", "CEO", 100000));
        company.add(techDept);
        company.add(hrDept);
        
        // 显示组织架构
        System.out.println("=== 公司组织架构 ===");
        company.display(0);
    }
}

输出:

=== 公司组织架构 ===
🏢 ABC科技有限公司 (总薪资: ¥287000.0)
  👤 老板 - CEO (¥100000.0)
  🏢 技术部 (总薪资: ¥152000.0)
    👤 张三 - 技术总监 (¥50000.0)
    🏢 研发组 (总薪资: ¥62000.0)
      👤 李四 - 高级工程师 (¥30000.0)
      👤 王五 - 中级工程师 (¥20000.0)
      👤 赵六 - 初级工程师 (¥12000.0)
    🏢 测试组 (总薪资: ¥40000.0)
      👤 钱七 - 测试经理 (¥25000.0)
      👤 孙八 - 测试工程师 (¥15000.0)
  🏢 人事部 (总薪资: ¥35000.0)
    👤 周九 - 人事经理 (¥25000.0)
    👤 吴十 - 招聘专员 (¥10000.0)

📊 类图结构

              ┌────────────────┐
              │   Component    │ ◄─────────────┐
              │ + operation()  │               │
              │ + add()        │               │
              │ + remove()     │               │
              └───────▲────────┘               │
                      │                        │
         ┌────────────┴────────────┐           │
         │                         │           │
    ┌────┴────┐              ┌─────┴─────┐     │
    │  Leaf   │              │ Composite │     │ 包含
    │+operation│              │+operation │ ────┘
    └─────────┘              │+add()     │
                             │+remove()  │
                             └───────────┘

✅ 适用场景

  1. 表示树形结构:如文件系统、组织架构、菜单
  2. 统一处理:希望统一处理叶子和容器对象
  3. 递归结构:整体与部分具有相同的操作

⚠️ 透明模式 vs 安全模式

模式特点优点缺点
透明模式所有方法都在抽象类中声明叶子和容器完全一致叶子节点调用 add/remove 会出错
安全模式add/remove 只在容器中声明不会误操作需要区分叶子和容器

上面的例子使用的是透明模式(在抽象类中声明了 add/remove)。


小结

组合模式的核心:将对象组合成树形结构,统一叶子和容器的操作。

适合场景:任何"整体-部分"的层次结构,如文件系统、组织架构、菜单等。

👉 下一篇:装饰器模式

Prev
桥接模式 (Bridge)
Next
装饰器模式 (Decorator)