本文目录导读:

我将为你展示组合模式的完整案例,从基础概念到实际应用。
什么是组合模式?
组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次关系,让客户端可以一致地处理单个对象和组合对象。
基础案例:文件系统
1 组件接口
// 抽象组件
public abstract class FileSystemComponent {
protected String name;
public FileSystemComponent(String name) {
this.name = name;
}
public abstract void display(int depth);
public abstract long getSize();
// 默认实现,叶子节点不需要重写
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException();
}
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException();
}
public FileSystemComponent getChild(int index) {
throw new UnsupportedOperationException();
}
protected String getIndent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
return sb.toString();
}
}
2 叶子节点 - 文件
// 叶子节点:文件
public class File extends FileSystemComponent {
private long size; // 文件大小(字节)
public File(String name, long size) {
super(name);
this.size = size;
}
@Override
public void display(int depth) {
System.out.println(getIndent(depth) + "📄 " + name + " (" + formatSize(size) + ")");
}
@Override
public long getSize() {
return size;
}
private String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
}
}
3 复合节点 - 文件夹
// 复合节点:文件夹
public class Folder extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
private Date createDate;
public Folder(String name) {
super(name);
this.createDate = new Date();
}
@Override
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void remove(FileSystemComponent component) {
children.remove(component);
}
@Override
public FileSystemComponent getChild(int index) {
return children.get(index);
}
@Override
public void display(int depth) {
System.out.println(getIndent(depth) + "📁 " + name + "/");
for (FileSystemComponent child : children) {
child.display(depth + 1);
}
}
@Override
public long getSize() {
long totalSize = 0;
for (FileSystemComponent child : children) {
totalSize += child.getSize();
}
return totalSize;
}
public int getChildCount() {
return children.size();
}
public boolean isEmpty() {
return children.isEmpty();
}
public List<FileSystemComponent> getChildren() {
return children;
}
}
4 客户端测试代码
public class FileSystemClient {
public static void main(String[] args) {
// 创建文件系统结构
Folder root = new Folder("C:");
// 创建目录结构
Folder windows = new Folder("Windows");
Folder programFiles = new Folder("Program Files");
Folder users = new Folder("Users");
// Windows目录内容
File system32 = new File("system32.dll", 5 * 1024 * 1024);
File explorer = new File("explorer.exe", 3 * 1024 * 1024);
windows.add(system32);
windows.add(explorer);
// Program Files目录内容
Folder java = new Folder("Java");
Folder python = new Folder("Python");
programFiles.add(java);
programFiles.add(python);
// Java目录内容
File javac = new File("javac.exe", 2 * 1024 * 1024);
File jar = new File("jar.exe", 1 * 1024 * 1024);
java.add(javac);
java.add(jar);
// Users目录内容
File report = new File("report.doc", 2048);
File photo = new File("photo.jpg", 5 * 1024 * 1024);
users.add(report);
users.add(photo);
// 组装树结构
root.add(windows);
root.add(programFiles);
root.add(users);
// 显示整个文件系统
System.out.println("=== 文件系统结构 ===");
root.display(0);
// 计算总大小
System.out.println("\n=== 总大小统计 ===");
System.out.println("C: 总大小: " + formatSize(root.getSize()));
System.out.println("Windows: " + formatSize(windows.getSize()));
System.out.println("Program Files: " + formatSize(programFiles.getSize()));
System.out.println("Users: " + formatSize(users.getSize()));
// 统一处理叶子节点和复合节点
System.out.println("\n=== 统一处理演示 ===");
processComponent(root);
}
// 统一处理方法,不需要区分是文件还是文件夹
private static void processComponent(FileSystemComponent component) {
System.out.println("处理: " + component.name);
if (component instanceof Folder) {
Folder folder = (Folder) component;
for (FileSystemComponent child : folder.getChildren()) {
processComponent(child);
}
}
}
private static String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
}
}
进阶案例:组织结构管理
1 组织架构组件
// 抽象组件
public abstract class OrganizationComponent {
protected String name;
protected String position;
public OrganizationComponent(String name, String position) {
this.name = name;
this.position = position;
}
public abstract void show(int depth);
public abstract double calculateSalary();
public abstract int getEmployeeCount();
// 树形操作方法
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
public OrganizationComponent getChild(int index) {
throw new UnsupportedOperationException();
}
protected String getIndent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
return sb.toString();
}
}
2 员工(叶子节点)
public class Employee extends OrganizationComponent {
private double baseSalary;
private double bonus;
private String department;
public Employee(String name, String position, String department,
double baseSalary, double bonus) {
super(name, position);
this.department = department;
this.baseSalary = baseSalary;
this.bonus = bonus;
}
@Override
public void show(int depth) {
System.out.println(getIndent(depth) + "👤 " + name + " | " + position +
" | " + department + " | 薪资: " + calculateSalary());
}
@Override
public double calculateSalary() {
return baseSalary + bonus;
}
@Override
public int getEmployeeCount() {
return 1;
}
}
3 部门(复合节点)
public class Department extends OrganizationComponent {
private List<OrganizationComponent> members = new ArrayList<>();
private String description;
private double departmentBonus;
public Department(String name, String position, String description) {
super(name, position);
this.description = description;
this.departmentBonus = 0;
}
@Override
public void add(OrganizationComponent component) {
members.add(component);
}
@Override
public void remove(OrganizationComponent component) {
members.remove(component);
}
@Override
public OrganizationComponent getChild(int index) {
return members.get(index);
}
@Override
public void show(int depth) {
System.out.println(getIndent(depth) + "🏢 [" + name + "] " +
position + " - " + description);
for (OrganizationComponent member : members) {
member.show(depth + 1);
}
}
@Override
public double calculateSalary() {
double total = 0;
for (OrganizationComponent member : members) {
total += member.calculateSalary();
}
return total;
}
@Override
public int getEmployeeCount() {
int count = 0;
for (OrganizationComponent member : members) {
count += member.getEmployeeCount();
}
return count;
}
// 部门特有的方法
public void setDepartmentBonus(double bonus) {
this.departmentBonus = bonus;
}
public String getDescription() {
return description;
}
}
4 客户端测试
public class OrganizationClient {
public static void main(String[] args) {
// 创建公司结构
Department company = new Department("TechCorp", "总公司", "科技创新公司");
// 创建部门
Department techDept = new Department("技术部", "技术部门", "负责产品研发");
Department salesDept = new Department("销售部", "销售部门", "负责市场销售");
Department hrDept = new Department("人力资源部", "人事部门", "负责人员管理");
// 技术部门员工
Employee techManager = new Employee("张三", "技术总监", "技术部", 30000, 10000);
Employee dev1 = new Employee("李四", "高级开发工程师", "技术部", 20000, 5000);
Employee dev2 = new Employee("王五", "开发工程师", "技术部", 15000, 3000);
// 创建技术部子团队
Department frontendTeam = new Department("前端组", "团队", "负责前端开发");
Department backendTeam = new Department("后端组", "团队", "负责后端开发");
Employee frontend1 = new Employee("赵六", "前端工程师", "技术部", 12000, 2000);
Employee frontend2 = new Employee("钱七", "前端工程师", "技术部", 12000, 2000);
Department frontendArchitect = new Department("架构组", "子团队", "负责架构设计");
frontendTeam.add(frontend1);
frontendTeam.add(frontend2);
frontendArchitect.add(new Employee("孙八", "首席架构师", "技术部", 25000, 8000));
frontendTeam.add(frontendArchitect);
backendTeam.add(dev1);
backendTeam.add(dev2);
techDept.add(techManager);
techDept.add(frontendTeam);
techDept.add(backendTeam);
// 销售部门
Employee salesManager = new Employee("周九", "销售总监", "销售部", 15000, 10000);
Employee sales1 = new Employee("吴十", "销售代表", "销售部", 8000, 2000);
Employee sales2 = new Employee("郑十一", "销售代表", "销售部", 8000, 2000);
salesDept.add(salesManager);
salesDept.add(sales1);
salesDept.add(sales2);
// 人力资源部门
Employee hrManager = new Employee("王十二", "HR总监", "人力资源部", 10000, 3000);
Employee hr1 = new Employee("李十三", "HR", "人力资源部", 5000, 1000);
hrDept.add(hrManager);
hrDept.add(hr1);
// 组装公司结构
company.add(techDept);
company.add(salesDept);
company.add(hrDept);
// 显示公司组织架构
System.out.println("=== 公司组织架构 ===");
company.show(0);
// 统计信息
System.out.println("\n=== 公司统计信息 ===");
System.out.println("员工总数: " + company.getEmployeeCount() + " 人");
System.out.println("月度总薪资: ¥" + String.format("%,.2f", company.calculateSalary()));
// 统计各部门
System.out.println("\n=== 各部门统计 ===");
statDepartment(techDept, "技术部");
statDepartment(salesDept, "销售部");
statDepartment(hrDept, "人力资源部");
// 安全删除操作
System.out.println("\n=== 安全删除测试 ===");
try {
Employee emp = new Employee("测试", "测试", "测试", 0, 0);
emp.show(0); // 叶子节点可以显示
emp.add(new Employee("非法", "操作", "测试", 0, 0)); // 会抛出异常
} catch (UnsupportedOperationException e) {
System.out.println("正确抛出异常: " + e.getMessage());
}
}
private static void statDepartment(Department dept, String name) {
System.out.println(name + ": " + dept.getEmployeeCount() + " 人, " +
"薪资: ¥" + String.format("%,.2f", dept.calculateSalary()));
}
}
安全组合 vs 透明组合
1 安全组合模式(推荐)
// 安全模式:接口只定义叶子节点的方法
public abstract class SafeComponent {
protected String name;
public SafeComponent(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void operation();
}
// 叶子节点
public class Leaf extends SafeComponent {
public Leaf(String name) {
super(name);
}
@Override
public void operation() {
System.out.println("叶子节点: " + name + " 执行操作");
}
}
// 复合节点 - 有自己的管理模式
public class Composite extends SafeComponent {
private List<SafeComponent> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
public void add(SafeComponent component) {
children.add(component);
}
public void remove(SafeComponent component) {
children.remove(component);
}
public SafeComponent getChild(int index) {
return children.get(index);
}
@Override
public void operation() {
System.out.println("复合节点: " + name + " 执行操作");
for (SafeComponent child : children) {
child.operation();
}
}
}
实战案例:菜单系统
// 菜单组件接口
public abstract class MenuComponent {
protected String name;
protected String description;
public MenuComponent(String name, String description) {
this.name = name;
this.description = description;
}
public abstract void print();
public abstract double getPrice();
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
protected String getIndent(int depth) {
return " ".repeat(depth);
}
}
// 菜单项(叶子节点)
public class MenuItem extends MenuComponent {
private double price;
private boolean vegetarian;
public MenuItem(String name, String description, double price, boolean vegetarian) {
super(name, description);
this.price = price;
this.vegetarian = vegetarian;
}
@Override
public void print() {
System.out.println(" " + name + (vegetarian ? "(素食)" : "") +
" - $" + price);
System.out.println(" " + description);
}
@Override
public double getPrice() {
return price;
}
}
// 菜单(复合节点)
public class Menu extends MenuComponent {
private List<MenuComponent> menuComponents = new ArrayList<>();
public Menu(String name, String description) {
super(name, description);
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
@Override
public void print() {
System.out.println("\n=== " + name + " ===");
System.out.println(description);
System.out.println("---------------------");
for (MenuComponent component : menuComponents) {
component.print();
}
}
@Override
public double getPrice() {
double total = 0;
for (MenuComponent component : menuComponents) {
total += component.getPrice();
}
return total;
}
}
// 测试代码
public class MenuClient {
public static void main(String[] args) {
// 创建总菜单
Menu allMenu = new Menu("全部菜单", "所有菜单选项");
// 创建分类菜单
Menu breakfastMenu = new Menu("早餐菜单", "早晨特供");
Menu lunchMenu = new Menu("午餐菜单", "中午精选");
Menu dinnerMenu = new Menu("晚餐菜单", "傍晚推荐");
// 添加早餐菜单项
breakfastMenu.add(new MenuItem("鸡蛋三明治", "全麦面包配煎蛋和蔬菜", 5.99, false));
breakfastMenu.add(new MenuItem("燕麦粥", "新鲜燕麦配水果", 3.99, true));
// 创建午餐子菜单
Menu mainCourseMenu = new Menu("主菜", "午餐主菜");
Menu dessertMenu = new Menu("甜点", "美味甜点");
mainCourseMenu.add(new MenuItem("烤鸡沙拉", "新鲜蔬菜配烤鸡胸肉", 8.99, false));
mainCourseMenu.add(new MenuItem("素食意面", "番茄酱配意大利面", 6.99, true));
dessertMenu.add(new MenuItem("提拉米苏", "传统意大利甜点", 4.99, true));
dessertMenu.add(new MenuItem("草莓蛋糕", "新鲜草莓配奶油蛋糕", 3.99, true));
lunchMenu.add(mainCourseMenu);
lunchMenu.add(dessertMenu);
// 添加晚餐菜单项
dinnerMenu.add(new MenuItem("牛排套餐", "精选牛肉配时蔬", 19.99, false));
dinnerMenu.add(new MenuItem("烤鱼", "时令鲜鱼配柠檬汁", 15.99, false));
// 组装菜单
allMenu.add(breakfastMenu);
allMenu.add(lunchMenu);
allMenu.add(dinnerMenu);
// 打印菜单
allMenu.print();
// 计算总价
System.out.println("\n=== 总价计算 ===");
System.out.printf("全部菜单总价: $%.2f%n", allMenu.getPrice());
System.out.printf("午餐菜单总价: $%.2f%n", lunchMenu.getPrice());
System.out.printf("早餐菜单总价: $%.2f%n", breakfastMenu.getPrice());
}
}
组合模式总结
1 模式结构
┌─────────────────────────────────────────────────────┐
│ Component (抽象组件) │
│ - 定义所有对象的通用方法 │
│ - 提供默认实现 │
└─────────────────────────────────────────────────────┘
▲ ▲
/ \ |
┌─────────────┘ └─────────────┐ |
│ │ │
┌──────────────┐ ┌────────────────┐
│ Leaf (叶子) │ │ Composite (复合) │
│ - 没有子节点 │ │ - 包含子节点 │
│ - 实现具体操作 │ │ - 管理子节点 │
└──────────────┘ └────────────────┘
2 优缺点
优点:
- 统一接口:客户端可以一致地处理单个对象和组合对象
- 简化客户端代码:不需要区分处理叶子节点和复合节点
- 易于扩展:可以轻松增加新的组件类型
- 天然递归:适合处理树形结构
缺点:
- 设计泛化:可能会让设计过于一般化,难以限制组件类型
- 性能问题:对树进行遍历时可能会有性能开销
- 安全与透明权衡:需要权衡安全组合和透明组合
3 适用场景
- 树形结构:文件系统、组织架构、菜单系统
- 递归结构:XML/HTML解析、JSON处理
- 图形界面:容器组件和独立组件
- 数据模型:目录树、分类树
4 实际应用案例
// Java AWT/Swing中的容器层次
// Java标准库中的目录结构
// 工作流引擎中的任务分解
// 电商系统中的商品分类
// 实际项目示例:多级分类
public class CategoryExample {
public static void main(String[] args) {
CategoryComponent root = new Category("电子产品");
CategoryComponent phone = new Category("手机");
CategoryComponent notebook = new Category("笔记本");
phone.add(new Product("iPhone 15", 6999));
phone.add(new Product("华为 Mate 60", 5999));
CategoryComponent gaming = new Category("游戏本");
gaming.add(new Product("联想拯救者", 7999));
notebook.add(gaming);
root.add(phone);
root.add(notebook);
// 显示所有产品
root.print();
}
}
组合模式是处理树形结构数据的重要设计模式,在实际开发中应用广泛,掌握了它能够让代码更加简洁、灵活和可扩展。