设计模式之组合模式详解 - 指南

深入理解设计模式之组合模式(Composite Pattern)

一、引言

在软件开发中,我们经常需要处理树形结构的数据。比如:文件系统中的文件和文件夹、公司的组织架构、菜单系统、XML文档结构等。这些场景有一个共同的特点:它们都是树形结构,并且叶子节点和容器节点在某些操作上需要保持一致性。

传统的处理方式是为叶子节点和容器节点定义不同的类,客户端需要区分它们并使用不同的方式处理。这会导致客户端代码复杂,耦合度高,难以维护。

传统方式的问题:
if (node instanceof File) {
    File file = (File) node;
    file.display();
} else if (node instanceof Folder) {
    Folder folder = (Folder) node;
    folder.display();
    for (Component child : folder.getChildren()) {
        // 递归处理...需要再次判断类型
    }
}
代码充满了类型判断,难以扩展

组合模式为这类问题提供了优雅的解决方案。它就像现实中的文件系统:无论是文件还是文件夹,你都可以用统一的方式打开、删除、移动它们;文件夹可以包含文件,也可以包含其他文件夹,形成树形结构。

本文将深入探讨组合模式的原理、实现方式,并结合文件系统、组织架构、Swing组件树等实际应用,帮助你全面掌握这一重要的设计模式。

二、什么是组合模式

2.1 定义

组合模式(Composite Pattern)是一种结构型设计模式,它将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

2.2 核心思想

  • 统一对待:叶子节点和容器节点实现相同的接口,客户端统一对待
  • 树形结构:将对象组合成树形结构表示层次关系
  • 递归组合:容器对象可以包含叶子对象,也可以包含其他容器对象
  • 透明性:客户端无需区分叶子节点和容器节点

2.3 模式结构

组合模式结构:
┌───────────────────────┐
│   <>       │
│     Component         │  抽象组件
├───────────────────────┤
│ + operation()         │  通用操作
│ + add(Component)      │  添加子节点
│ + remove(Component)   │  移除子节点
│ + getChild(int)       │  获取子节点
└──────────┬────────────┘
           △
           │ 实现
    ┌──────┴──────┐
    │             │
┌───┴──────┐  ┌──┴─────────────┐
│  Leaf    │  │   Composite    │  容器节点
├──────────┤  ├────────────────┤
│operation │  │- children: List│  包含子节点
└──────────┘  │+ operation()   │
              │+ add()         │
              │+ remove()      │
              │+ getChild()    │
              └────────────────┘
树形结构示例:
         Composite(root)
         /     |      \
        /      |       \
    Leaf1  Composite2  Leaf2
              /   \
             /     \
         Leaf3    Leaf4
客户端代码:
Component component = getComponent();  // 可能是Leaf或Composite
component.operation();  // 统一调用,无需判断类型

2.4 组合模式的两种实现

1. 透明方式(Transparent):
   - 在Component中声明所有方法(包括管理子节点的方法)
   - 优点:客户端完全透明,无需区分类型
   - 缺点:Leaf需要实现管理方法(但实际不支持)
   Component
   ├─ operation()
   ├─ add()       ← Leaf也有这些方法(但会抛异常)
   ├─ remove()
   └─ getChild()
2. 安全方式(Safe):
   - 在Component中只声明共同方法
   - 管理方法只在Composite中定义
   - 优点:类型安全,Leaf不会有无效方法
   - 缺点:客户端需要区分Leaf和Composite
   Component
   └─ operation()  ← 只有通用方法
   Composite extends Component
   ├─ add()        ← 管理方法只在这里
   ├─ remove()
   └─ getChild()

三、基础示例

3.1 场景:文件系统

文件系统是组合模式最经典的应用场景。

抽象组件(透明方式):

import java.util.ArrayList;
import java.util.List;
/**
* 抽象组件:文件系统组件
*/
public abstract class FileSystemComponent {
protected String name;
public FileSystemComponent(String name) {
this.name = name;
}
/**
* 显示组件信息(通用方法)
*/
public abstract void display(int depth);
/**
* 添加子组件(容器方法)
* 叶子节点不支持,抛出异常
*/
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 移除子组件(容器方法)
*/
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 获取子组件(容器方法)
*/
public FileSystemComponent getChild(int index) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 获取大小
*/
public abstract long getSize();
public String getName() {
return name;
}
}

叶子节点:

/**
* 叶子节点:文件
*/
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) {
// 缩进显示
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append("  ");
}
System.out.println(indent + " 文件: " + name + " (" + size + " KB)");
}
@Override
public long getSize() {
return size;
}
}

容器节点:

/**
* 容器节点:文件夹
*/
public class Folder extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
  public Folder(String name) {
  super(name);
  }
  @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) {
  // 显示文件夹
  StringBuilder indent = new StringBuilder();
  for (int i = 0; i < depth; i++) {
  indent.append("  ");
  }
  System.out.println(indent + " 文件夹: " + name);
  // 递归显示所有子组件
  for (FileSystemComponent child : children) {
  child.display(depth + 1);
  }
  }
  @Override
  public long getSize() {
  // 文件夹大小 = 所有子组件大小之和
  long total = 0;
  for (FileSystemComponent child : children) {
  total += child.getSize();
  }
  return total;
  }
  public List<FileSystemComponent> getChildren() {
    return children;
    }
    }

客户端使用:

public class FileSystemDemo {
public static void main(String[] args) {
// 创建根文件夹
Folder root = new Folder("根目录");
// 创建documents文件夹
Folder documents = new Folder("Documents");
documents.add(new File("简历.docx", 50));
documents.add(new File("项目报告.pdf", 120));
// 创建photos文件夹
Folder photos = new Folder("Photos");
photos.add(new File("照片1.jpg", 300));
photos.add(new File("照片2.jpg", 280));
// 创建photos的子文件夹
Folder vacation = new Folder("Vacation");
vacation.add(new File("海滩.jpg", 400));
vacation.add(new File("山景.jpg", 350));
photos.add(vacation);
// 创建music文件夹
Folder music = new Folder("Music");
music.add(new File("歌曲1.mp3", 5000));
music.add(new File("歌曲2.mp3", 4800));
// 组装树形结构
root.add(documents);
root.add(photos);
root.add(music);
root.add(new File("README.txt", 5));
// 显示整个文件系统(统一操作)
System.out.println("========== 文件系统结构 ==========\n");
root.display(0);
// 计算总大小(统一操作)
System.out.println("\n========== 大小统计 ==========");
System.out.println("根目录总大小: " + root.getSize() + " KB");
System.out.println("Documents大小: " + documents.getSize() + " KB");
System.out.println("Photos大小: " + photos.getSize() + " KB");
// 演示透明性:客户端无需区分File和Folder
System.out.println("\n========== 演示透明性 ==========");
processComponent(root);
processComponent(documents);
processComponent(new File("test.txt", 10));
}
/**
* 统一处理组件(无需判断类型)
*/
private static void processComponent(FileSystemComponent component) {
System.out.println("处理组件: " + component.getName() +
", 大小: " + component.getSize() + " KB");
}
}

输出:

========== 文件系统结构 ==========
 文件夹: 根目录
   文件夹: Documents
     文件: 简历.docx (50 KB)
     文件: 项目报告.pdf (120 KB)
   文件夹: Photos
     文件: 照片1.jpg (300 KB)
     文件: 照片2.jpg (280 KB)
     文件夹: Vacation
       文件: 海滩.jpg (400 KB)
       文件: 山景.jpg (350 KB)
   文件夹: Music
     文件: 歌曲1.mp3 (5000 KB)
     文件: 歌曲2.mp3 (4800 KB)
   文件: README.txt (5 KB)
========== 大小统计 ==========
根目录总大小: 11305 KB
Documents大小: 170 KB
Photos大小: 1330 KB
========== 演示透明性 ==========
处理组件: 根目录, 大小: 11305 KB
处理组件: Documents, 大小: 170 KB
处理组件: test.txt, 大小: 10 KB

四、实际生产场景应用

4.1 场景:企业组织架构

企业的组织架构天然就是树形结构。

/**
* 抽象组件:组织单元
*/
abstract class OrganizationComponent {
protected String name;
protected String position;
public OrganizationComponent(String name, String position) {
this.name = name;
this.position = position;
}
/**
* 显示组织结构
*/
public abstract void display(int depth);
/**
* 添加下属
*/
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
/**
* 移除下属
*/
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
/**
* 计算总人数
*/
public abstract int getEmployeeCount();
/**
* 计算总工资
*/
public abstract double getTotalSalary();
public String getName() { return name; }
public String getPosition() { return position; }
}
/**
* 叶子节点:员工
*/
class Employee extends OrganizationComponent {
private double salary;
public Employee(String name, String position, double salary) {
super(name, position);
this.salary = salary;
}
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append("  ");
}
System.out.println(indent + " " + position + ": " + name +
" (工资: ¥" + salary + ")");
}
@Override
public int getEmployeeCount() {
return 1;
}
@Override
public double getTotalSalary() {
return salary;
}
public double getSalary() {
return salary;
}
}
/**
* 容器节点:部门
*/
class Department extends OrganizationComponent {
private List<OrganizationComponent> members = new ArrayList<>();
  public Department(String name) {
  super(name, "部门");
  }
  @Override
  public void add(OrganizationComponent component) {
  members.add(component);
  }
  @Override
  public void remove(OrganizationComponent component) {
  members.remove(component);
  }
  @Override
  public void display(int depth) {
  StringBuilder indent = new StringBuilder();
  for (int i = 0; i < depth; i++) {
  indent.append("  ");
  }
  System.out.println(indent + " " + name);
  // 递归显示所有成员
  for (OrganizationComponent member : members) {
  member.display(depth + 1);
  }
  }
  @Override
  public int getEmployeeCount() {
  int count = 0;
  for (OrganizationComponent member : members) {
  count += member.getEmployeeCount();
  }
  return count;
  }
  @Override
  public double getTotalSalary() {
  double total = 0;
  for (OrganizationComponent member : members) {
  total += member.getTotalSalary();
  }
  return total;
  }
  }
  /**
  * 测试组织架构
  */
  class OrganizationDemo {
  public static void main(String[] args) {
  // 创建公司
  Department company = new Department("科技有限公司");
  // 创建技术部
  Department techDept = new Department("技术部");
  techDept.add(new Employee("张三", "技术总监", 30000));
  techDept.add(new Employee("李四", "高级工程师", 20000));
  techDept.add(new Employee("王五", "工程师", 15000));
  // 创建销售部
  Department salesDept = new Department("销售部");
  salesDept.add(new Employee("赵六", "销售总监", 25000));
  salesDept.add(new Employee("钱七", "销售经理", 18000));
  // 创建销售部的子部门
  Department regionalSales = new Department("华东区销售");
  regionalSales.add(new Employee("孙八", "区域经理", 16000));
  regionalSales.add(new Employee("周九", "销售代表", 12000));
  salesDept.add(regionalSales);
  // 创建人事部
  Department hrDept = new Department("人事部");
  hrDept.add(new Employee("吴十", "人事经理", 18000));
  hrDept.add(new Employee("郑十一", "招聘专员", 12000));
  // 组装公司结构
  company.add(new Employee("CEO", "首席执行官", 50000));
  company.add(techDept);
  company.add(salesDept);
  company.add(hrDept);
  // 显示组织结构
  System.out.println("========== 组织架构 ==========\n");
  company.display(0);
  // 统计信息
  System.out.println("\n========== 统计信息 ==========");
  System.out.println("总人数: " + company.getEmployeeCount());
  System.out.println("总工资: ¥" + company.getTotalSalary());
  System.out.println("技术部人数: " + techDept.getEmployeeCount());
  System.out.println("技术部工资: ¥" + techDept.getTotalSalary());
  }
  }

4.2 场景:菜单系统

Web应用中的多级菜单系统。

/**
* 抽象组件:菜单组件
*/
abstract class MenuComponent {
protected String name;
protected String url;
protected String icon;
public MenuComponent(String name, String url, String icon) {
this.name = name;
this.url = url;
this.icon = icon;
}
/**
* 渲染菜单
*/
public abstract void render(int level);
/**
* 添加子菜单
*/
public void add(MenuComponent component) {
throw new UnsupportedOperationException();
}
/**
* 移除子菜单
*/
public void remove(MenuComponent component) {
throw new UnsupportedOperationException();
}
/**
* 检查权限
*/
public abstract boolean hasPermission(String userId);
public String getName() { return name; }
public String getUrl() { return url; }
}
/**
* 叶子节点:菜单项
*/
class MenuItem extends MenuComponent {
private String permission;  // 权限标识
public MenuItem(String name, String url, String icon, String permission) {
super(name, url, icon);
this.permission = permission;
}
@Override
public void render(int level) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < level; i++) {
indent.append("  ");
}
System.out.println(indent + icon + " " + name + " -> " + url);
}
@Override
public boolean hasPermission(String userId) {
// 简化:实际应该查询数据库
return true;
}
}
/**
* 容器节点:菜单组
*/
class Menu extends MenuComponent {
private List<MenuComponent> children = new ArrayList<>();
  public Menu(String name, String icon) {
  super(name, null, icon);
  }
  @Override
  public void add(MenuComponent component) {
  children.add(component);
  }
  @Override
  public void remove(MenuComponent component) {
  children.remove(component);
  }
  @Override
  public void render(int level) {
  StringBuilder indent = new StringBuilder();
  for (int i = 0; i < level; i++) {
  indent.append("  ");
  }
  System.out.println(indent + icon + " " + name);
  // 递归渲染子菜单
  for (MenuComponent child : children) {
  child.render(level + 1);
  }
  }
  @Override
  public boolean hasPermission(String userId) {
  // 菜单组:只要有一个子菜单有权限就显示
  for (MenuComponent child : children) {
  if (child.hasPermission(userId)) {
  return true;
  }
  }
  return false;
  }
  /**
  * 获取有权限的子菜单
  */
  public List<MenuComponent> getAuthorizedChildren(String userId) {
    List<MenuComponent> result = new ArrayList<>();
      for (MenuComponent child : children) {
      if (child.hasPermission(userId)) {
      result.add(child);
      }
      }
      return result;
      }
      }
      /**
      * 测试菜单系统
      */
      class MenuSystemDemo {
      public static void main(String[] args) {
      // 创建菜单结构
      Menu mainMenu = new Menu("主菜单", "");
      // 系统管理菜单
      Menu systemMenu = new Menu("系统管理", "⚙️");
      systemMenu.add(new MenuItem("用户管理", "/system/users", "", "system:user"));
      systemMenu.add(new MenuItem("角色管理", "/system/roles", "", "system:role"));
      systemMenu.add(new MenuItem("权限管理", "/system/permissions", "", "system:permission"));
      // 业务管理菜单
      Menu businessMenu = new Menu("业务管理", "");
      Menu orderMenu = new Menu("订单管理", "");
      orderMenu.add(new MenuItem("订单列表", "/business/orders", "", "business:order:list"));
      orderMenu.add(new MenuItem("订单统计", "/business/orders/stats", "", "business:order:stats"));
      Menu productMenu = new Menu("商品管理", "");
      productMenu.add(new MenuItem("商品列表", "/business/products", "", "business:product:list"));
      productMenu.add(new MenuItem("分类管理", "/business/categories", "️", "business:category"));
      businessMenu.add(orderMenu);
      businessMenu.add(productMenu);
      // 报表中心
      Menu reportMenu = new Menu("报表中心", "");
      reportMenu.add(new MenuItem("销售报表", "/reports/sales", "", "report:sales"));
      reportMenu.add(new MenuItem("库存报表", "/reports/inventory", "", "report:inventory"));
      // 组装主菜单
      mainMenu.add(systemMenu);
      mainMenu.add(businessMenu);
      mainMenu.add(reportMenu);
      mainMenu.add(new MenuItem("个人中心", "/profile", "", "profile"));
      // 渲染完整菜单
      System.out.println("========== 完整菜单结构 ==========\n");
      mainMenu.render(0);
      // 模拟根据权限渲染菜单
      System.out.println("\n========== 根据权限过滤后的菜单 ==========\n");
      renderAuthorizedMenu(mainMenu, "user123", 0);
      }
      /**
      * 渲染有权限的菜单
      */
      private static void renderAuthorizedMenu(MenuComponent menu, String userId, int level) {
      if (menu.hasPermission(userId)) {
      menu.render(level);
      }
      }
      }

五、开源框架中的应用

5.1 AWT/Swing组件树

Java GUI框架使用组合模式构建组件树。

/**
* Swing组件树示例
*
* Component (抽象组件)
*    ├─ Container (容器组件)
*    │     ├─ JPanel
*    │     ├─ JFrame
*    │     └─ JDialog
*    └─ JComponent (叶子组件)
*          ├─ JButton
*          ├─ JLabel
*          └─ JTextField
*/
import javax.swing.*;
import java.awt.*;
class SwingCompositeDemo {
public static void main(String[] args) {
// 创建窗口(容器)
JFrame frame = new JFrame("组合模式示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
// 创建面板(容器)
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
// 添加组件(叶子)
panel.add(new JLabel("用户名:"));
panel.add(new JTextField(15));
panel.add(new JLabel("密码:"));
panel.add(new JPasswordField(15));
panel.add(new JButton("登录"));
// 容器包含容器
frame.add(panel);
frame.setVisible(true);
// 组合模式的体现:
// 1. Container和Component都是组件
// 2. Container可以包含Component
// 3. Container也可以包含其他Container
// 4. 统一的操作:setVisible(), setEnabled()等
}
}
/**
* 简化的Swing组件结构
*/
abstract class Component {
protected String name;
public abstract void paint();
public abstract void setVisible(boolean visible);
// 容器方法(透明方式)
public void add(Component component) {
throw new UnsupportedOperationException();
}
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
class Container extends Component {
private List<Component> children = new ArrayList<>();
  @Override
  public void paint() {
  System.out.println("绘制容器: " + name);
  for (Component child : children) {
  child.paint();
  }
  }
  @Override
  public void setVisible(boolean visible) {
  System.out.println((visible ? "显示" : "隐藏") + "容器: " + name);
  // 递归设置所有子组件
  for (Component child : children) {
  child.setVisible(visible);
  }
  }
  @Override
  public void add(Component component) {
  children.add(component);
  }
  @Override
  public void remove(Component component) {
  children.remove(component);
  }
  }
  class Button extends Component {
  @Override
  public void paint() {
  System.out.println("绘制按钮: " + name);
  }
  @Override
  public void setVisible(boolean visible) {
  System.out.println((visible ? "显示" : "隐藏") + "按钮: " + name);
  }
  }

5.2 MyBatis动态SQL

MyBatis的SqlNode使用组合模式构建动态SQL。

/**
* MyBatis的SqlNode接口(简化版)
*
* 用于构建动态SQL的树形结构
*/
interface SqlNode {
/**
* 应用节点,生成SQL片段
*/
boolean apply(DynamicContext context);
}
/**
* 动态上下文
*/
class DynamicContext {
private StringBuilder sqlBuilder = new StringBuilder();
private Map<String, Object> bindings = new HashMap<>();
  public void appendSql(String sql) {
  sqlBuilder.append(sql).append(" ");
  }
  public String getSql() {
  return sqlBuilder.toString().trim();
  }
  public Map<String, Object> getBindings() {
    return bindings;
    }
    }
    /**
    * 叶子节点:静态文本节点
    */
    class StaticTextSqlNode implements SqlNode {
    private String text;
    public StaticTextSqlNode(String text) {
    this.text = text;
    }
    @Override
    public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
    }
    }
    /**
    * 容器节点:混合SQL节点
    */
    class MixedSqlNode implements SqlNode {
    private List<SqlNode> contents;
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
        }
        @Override
        public boolean apply(DynamicContext context) {
        // 递归应用所有子节点
        for (SqlNode node : contents) {
        node.apply(context);
        }
        return true;
        }
        }
        /**
        * 条件节点:IF节点
        */
        class IfSqlNode implements SqlNode {
        private String test;
        private SqlNode contents;
        public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        }
        @Override
        public boolean apply(DynamicContext context) {
        // 简化:实际会解析test表达式
        if (evaluateTest(test, context)) {
        contents.apply(context);
        return true;
        }
        return false;
        }
        private boolean evaluateTest(String test, DynamicContext context) {
        // 简化实现
        return true;
        }
        }
        /**
        * 测试MyBatis SqlNode
        */
        class MyBatisSqlNodeDemo {
        public static void main(String[] args) {
      // 构建SQL树:SELECT * FROM users WHERE 1=1 <if test="name != null">AND name = #{name}</if>
        List<SqlNode> nodes = new ArrayList<>();
          nodes.add(new StaticTextSqlNode("SELECT * FROM users WHERE 1=1"));
          // IF节点
          SqlNode ifContents = new StaticTextSqlNode("AND name = #{name}");
          nodes.add(new IfSqlNode(ifContents, "name != null"));
          // 创建根节点
          MixedSqlNode rootNode = new MixedSqlNode(nodes);
          // 应用节点,生成SQL
          DynamicContext context = new DynamicContext();
          rootNode.apply(context);
          System.out.println("生成的SQL: " + context.getSql());
          System.out.println("\n组合模式的体现:");
          System.out.println("1. SqlNode是抽象组件");
          System.out.println("2. StaticTextSqlNode是叶子节点");
          System.out.println("3. MixedSqlNode、IfSqlNode是容器节点");
          System.out.println("4. 可以组合成复杂的动态SQL树");
          }
          }

六、组合模式的优缺点

6.1 优点

1. 统一对待单个对象和组合对象

// 无需类型判断
void process(Component component) {
component.operation();  // 统一调用
}
// 而不是:
void process(Object obj) {
if (obj instanceof Leaf) {
((Leaf) obj).operation();
} else if (obj instanceof Composite) {
((Composite) obj).operation();
}
}

2. 简化客户端代码

客户端不需要关心:
- 是叶子节点还是容器节点
- 树的层次结构
- 递归遍历逻辑
只需统一调用接口方法即可

3. 易于扩展

添加新的组件类型:
只需实现Component接口
不影响现有代码

4. 符合开闭原则

对扩展开放:可以添加新的Leaf或Composite
对修改封闭:无需修改现有代码

6.2 缺点

1. 设计较为抽象

需要合理设计组件接口
需要理解递归结构
新手可能难以理解

2. 限制组件类型困难

Composite可以包含任何Component
如果想限制只能包含特定类型,实现起来困难

3. 透明方式的问题

Leaf需要实现管理方法(如add、remove)
但实际不支持,只能抛异常
违反了类型安全

七、最佳实践

7.1 选择合适的实现方式

/**
* 透明方式 vs 安全方式
*/
// 1. 透明方式:适用于希望客户端完全透明的场景
abstract class TransparentComponent {
public abstract void operation();
public void add(TransparentComponent c) {
throw new UnsupportedOperationException();
}
public void remove(TransparentComponent c) {
throw new UnsupportedOperationException();
}
}
// 2. 安全方式:适用于类型安全要求高的场景
interface SafeComponent {
void operation();
}
class SafeComposite implements SafeComponent {
private List<SafeComponent> children = new ArrayList<>();
  @Override
  public void operation() {
  for (SafeComponent child : children) {
  child.operation();
  }
  }
  // 只在Composite中定义管理方法
  public void add(SafeComponent component) {
  children.add(component);
  }
  public void remove(SafeComponent component) {
  children.remove(component);
  }
  }

7.2 提供遍历器

/**
* 提供迭代器遍历组合结构
*/
class CompositeWithIterator extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
  public CompositeWithIterator(String name) {
  super(name);
  }
  /**
  * 提供迭代器
  */
  public Iterator<FileSystemComponent> iterator() {
    return children.iterator();
    }
    /**
    * 深度优先遍历
    */
    public Iterator<FileSystemComponent> deepIterator() {
      return new CompositeIterator(children.iterator());
      }
      // ... 其他方法
      }
      /**
      * 组合迭代器(深度优先)
      */
      class CompositeIterator implements Iterator<FileSystemComponent> {
        private Stack<Iterator<FileSystemComponent>> stack = new Stack<>();
          public CompositeIterator(Iterator<FileSystemComponent> iterator) {
            stack.push(iterator);
            }
            @Override
            public boolean hasNext() {
            if (stack.isEmpty()) {
            return false;
            }
            Iterator<FileSystemComponent> iterator = stack.peek();
              if (!iterator.hasNext()) {
              stack.pop();
              return hasNext();
              }
              return true;
              }
              @Override
              public FileSystemComponent next() {
              if (hasNext()) {
              Iterator<FileSystemComponent> iterator = stack.peek();
                FileSystemComponent component = iterator.next();
                // 如果是容器,将其子元素压入栈
                if (component instanceof CompositeWithIterator) {
                stack.push(((CompositeWithIterator) component).iterator());
                }
                return component;
                }
                return null;
                }
                }

7.3 缓存计算结果

/**
* 缓存昂贵的计算结果
*/
class CachedComposite extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
  private Long cachedSize = null;  // 缓存大小
  public CachedComposite(String name) {
  super(name);
  }
  @Override
  public void add(FileSystemComponent component) {
  children.add(component);
  // 添加子组件时清除缓存
  cachedSize = null;
  }
  @Override
  public void remove(FileSystemComponent component) {
  children.remove(component);
  // 移除子组件时清除缓存
  cachedSize = null;
  }
  @Override
  public long getSize() {
  if (cachedSize == null) {
  // 计算并缓存
  long total = 0;
  for (FileSystemComponent child : children) {
  total += child.getSize();
  }
  cachedSize = total;
  }
  return cachedSize;
  }
  @Override
  public void display(int depth) {
  // 实现显示逻辑
  }
  }

7.4 使用访问者模式增强

/**
* 结合访问者模式处理不同操作
*/
interface ComponentVisitor {
void visitFile(File file);
void visitFolder(Folder folder);
}
abstract class VisitableComponent extends FileSystemComponent {
public VisitableComponent(String name) {
super(name);
}
public abstract void accept(ComponentVisitor visitor);
}
/**
* 计算文件数量的访问者
*/
class FileCountVisitor implements ComponentVisitor {
private int fileCount = 0;
@Override
public void visitFile(File file) {
fileCount++;
}
@Override
public void visitFolder(Folder folder) {
// 文件夹本身不计数
}
public int getFileCount() {
return fileCount;
}
}

八、总结

8.1 核心要点

  1. 组合模式的本质:将对象组合成树形结构,统一对待单个对象和组合对象
  2. 关键角色
    • Component:抽象组件,定义统一接口
    • Leaf:叶子节点,不能包含子节点
    • Composite:容器节点,可以包含子节点
  3. 适用场景
    • 需要表示对象的部分-整体层次结构
    • 希望统一对待单个对象和组合对象
    • 树形结构的数据
  4. 核心优势
    • 简化客户端代码
    • 易于扩展
    • 符合开闭原则

8.2 使用建议

选择组合模式的检查清单:
✓ 数据结构是否是树形的?
✓ 是否需要统一对待叶子和容器?
✓ 是否需要递归处理?
✓ 是否希望客户端代码简单?
如果有3个以上"是",建议使用组合模式!

8.3 与其他模式的对比

组合 vs 装饰者:
- 组合:关注部分-整体关系,一对多
- 装饰者:关注动态添加职责,一对一
组合 vs 迭代器:
- 组合:定义树形结构
- 迭代器:遍历组合结构
组合 vs 访问者:
- 组合:定义结构
- 访问者:对结构执行操作

8.4 实践经验

  1. 合理选择实现方式:透明方式 vs 安全方式
  2. 提供便利方法:如迭代器、深度优先遍历等
  3. 考虑性能优化:缓存计算结果,避免重复计算
  4. 注意内存管理:深层嵌套可能导致栈溢出
  5. 结合其他模式:可以与访问者、迭代器等模式结合

posted @ 2026-02-07 12:36  clnchanpin  阅读(13)  评论(0)    收藏  举报