设计模式之组合模式

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

一、引言

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

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

ini 复制代码
传统方式的问题:

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 模式结构

scss 复制代码
组合模式结构:

┌───────────────────────┐
│   <<interface>>       │
│     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 组合模式的两种实现

scss 复制代码
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 场景:文件系统

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

抽象组件(透明方式):

java 复制代码
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;
    }
}

叶子节点:

java 复制代码
/**
 * 叶子节点:文件
 */
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;
    }
}

容器节点:

java 复制代码
/**
 * 容器节点:文件夹
 */
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;
    }
}

客户端使用:

java 复制代码
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");
    }
}

输出:

makefile 复制代码
========== 文件系统结构 ==========

📁 文件夹: 根目录
  📁 文件夹: 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 场景:企业组织架构

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

java 复制代码
/**
 * 抽象组件:组织单元
 */
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应用中的多级菜单系统。

java 复制代码
/**
 * 抽象组件:菜单组件
 */
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框架使用组合模式构建组件树。

java 复制代码
/**
 * 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。

java 复制代码
/**
 * 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. 统一对待单个对象和组合对象

java 复制代码
// 无需类型判断
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. 简化客户端代码

diff 复制代码
客户端不需要关心:
- 是叶子节点还是容器节点
- 树的层次结构
- 递归遍历逻辑

只需统一调用接口方法即可

3. 易于扩展

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

4. 符合开闭原则

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

6.2 缺点

1. 设计较为抽象

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

2. 限制组件类型困难

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

3. 透明方式的问题

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

七、最佳实践

7.1 选择合适的实现方式

java 复制代码
/**
 * 透明方式 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 提供遍历器

java 复制代码
/**
 * 提供迭代器遍历组合结构
 */
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 缓存计算结果

java 复制代码
/**
 * 缓存昂贵的计算结果
 */
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 使用访问者模式增强

java 复制代码
/**
 * 结合访问者模式处理不同操作
 */
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 使用建议

arduino 复制代码
选择组合模式的检查清单:

✓ 数据结构是否是树形的?
✓ 是否需要统一对待叶子和容器?
✓ 是否需要递归处理?
✓ 是否希望客户端代码简单?

如果有3个以上"是",建议使用组合模式!

8.3 与其他模式的对比

diff 复制代码
组合 vs 装饰者:
- 组合:关注部分-整体关系,一对多
- 装饰者:关注动态添加职责,一对一

组合 vs 迭代器:
- 组合:定义树形结构
- 迭代器:遍历组合结构

组合 vs 访问者:
- 组合:定义结构
- 访问者:对结构执行操作

8.4 实践经验

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

相关推荐
半路_出家ren42 分钟前
Tomcat下配置woniusales
java·数据库·mysql·网络安全·adb·tomcat·firewalld
tgethe44 分钟前
MybatisPlus基础部分详解(下篇)
java·spring boot·mybatisplus
f***686044 分钟前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
f***45321 小时前
JavaWeb项目打包、部署至Tomcat并启动的全程指南(图文详解)
java·tomcat
m***66731 小时前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
CoderYanger1 小时前
递归、搜索与回溯-穷举vs暴搜vs深搜vs回溯vs剪枝:13.子集
java·算法·leetcode·机器学习·剪枝·1024程序员节
f***6511 小时前
spring 跨域CORS Filter
java·后端·spring
hhwyqwqhhwy1 小时前
linux 设备树内容和plateform_device
java·linux·数据库
Overt0p1 小时前
抽奖系统 (1)
java·spring boot·spring·java-ee·java-rabbitmq