java设计模式八、组合模式

概述

组合模式是一种常用的结构型设计模式,它通过将对象组合成树形结构来表示"部分-整体"的层次关系。组合模式让客户端可以统一地处理单个对象和组合对象,无需关心自己处理的是单个对象还是整个组合结构。这种模式不仅提高了代码的复用性,还大大简化了复杂层次结构的处理逻辑。

什么是组合模式

组合模式(Composite Pattern)由GoF在《设计模式》一书中提出,它允许你将对象组合成树形结构来表示部分-整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性,这是该模式的核心思想。

核心思想

想象一下公司的组织架构:公司由多个部门组成,每个部门又可能包含子部门或员工。当我们需要统计整个公司的人数时,我们希望能够用同样的方式处理整个公司、单个部门或者某个员工。组合模式正是为了解决这种"部分-整体"层次结构的问题而诞生的。

核心优势

统一处理 :客户端可以一致地处理单个对象和组合对象,无需进行类型判断
简化客户端代码 :复杂的层次结构对客户端透明,使用简单
灵活扩展 :新增组件类型容易,符合开闭原则
层次结构清晰:天然支持树形结构的表示和操作

组合模式的三种角色

1. 组件接口(Component)

组件接口定义了所有组件(包括叶子节点和复合节点)的公共操作,这是整个模式的基础。

java 复制代码
package com.YA33.designpattern.composite;

/**
 * 文件系统组件接口 - 定义所有文件系统组件的公共操作
 * 作者:YA33
 */
public interface FileSystemComponent {
    
    /**
     * 显示组件信息
     * @param indent 缩进级别,用于层次显示
     */
    void display(String indent);
    
    /**
     * 计算组件大小
     * @return 组件大小(文件返回实际大小,文件夹返回包含内容的总大小)
     */
    long calculateSize();
    
    /**
     * 添加子组件(仅对文件夹有效)
     * @param component 要添加的组件
     */
    default void add(FileSystemComponent component) {
        throw new UnsupportedOperationException("添加操作不支持");
    }
    
    /**
     * 移除子组件(仅对文件夹有效)
     * @param component 要移除的组件
     */
    default void remove(FileSystemComponent component) {
        throw new UnsupportedOperationException("移除操作不支持");
    }
    
    /**
     * 获取子组件(仅对文件夹有效)
     * @param index 子组件索引
     * @return 子组件
     */
    default FileSystemComponent getChild(int index) {
        throw new UnsupportedOperationException("获取子组件操作不支持");
    }
}

2. 叶子节点(Leaf)

叶子节点是树形结构中的最小单元,没有子节点。在文件系统示例中,文件就是叶子节点。

java 复制代码
package com.YA33.designpattern.composite;

/**
 * 文件类 - 叶子节点,代表具体的文件
 * 作者:YA33
 */
public class File implements FileSystemComponent {
    private String name;
    private long size;
    private String type;
    
    public File(String name, long size, String type) {
        this.name = name;
        this.size = size;
        this.type = type;
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "📄 " + name + "." + type + " (" + formatSize(size) + ")");
    }
    
    @Override
    public long calculateSize() {
        // 文件的大小就是它本身的大小
        return size;
    }
    
    // 获取文件详细信息
    public String getFileInfo() {
        return name + "." + type + " - " + formatSize(size);
    }
    
    // 格式化文件大小显示
    private String formatSize(long size) {
        if (size < 1024) {
            return size + " B";
        } else if (size < 1024 * 1024) {
            return String.format("%.1f KB", size / 1024.0);
        } else {
            return String.format("%.1f MB", size / (1024.0 * 1024.0));
        }
    }
    
    // Getter方法
    public String getName() { return name; }
    public String getType() { return type; }
    public long getSize() { return size; }
}

3. 复合节点(Composite)

复合节点可以包含其他子节点,形成树形结构。在文件系统示例中,文件夹就是复合节点。

java 复制代码
package com.YA33.designpattern.composite;

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

/**
 * 文件夹类 - 复合节点,可以包含文件或其他文件夹
 * 作者:YA33
 */
public class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> children;
    
    public Folder(String name) {
        this.name = name;
        this.children = new ArrayList<>();
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "📁 " + name + " [" + formatSize(calculateSize()) + "]");
        
        // 递归显示所有子组件
        String newIndent = indent + "  ";
        for (FileSystemComponent child : children) {
            child.display(newIndent);
        }
    }
    
    @Override
    public long calculateSize() {
        long totalSize = 0;
        // 递归计算所有子组件的大小
        for (FileSystemComponent child : children) {
            totalSize += child.calculateSize();
        }
        return totalSize;
    }
    
    @Override
    public void add(FileSystemComponent component) {
        children.add(component);
    }
    
    @Override
    public void remove(FileSystemComponent component) {
        children.remove(component);
    }
    
    @Override
    public FileSystemComponent getChild(int index) {
        if (index >= 0 && index < children.size()) {
            return children.get(index);
        }
        return null;
    }
    
    /**
     * 搜索文件或文件夹
     * @param name 要搜索的名称
     * @return 匹配的组件列表
     */
    public List<FileSystemComponent> search(String name) {
        List<FileSystemComponent> results = new ArrayList<>();
        for (FileSystemComponent child : children) {
            if (child instanceof File) {
                File file = (File) child;
                if (file.getName().contains(name)) {
                    results.add(file);
                }
            } else if (child instanceof Folder) {
                Folder folder = (Folder) child;
                if (folder.name.contains(name)) {
                    results.add(folder);
                }
                // 递归搜索子文件夹
                results.addAll(folder.search(name));
            }
        }
        return results;
    }
    
    /**
     * 获取文件夹中的文件数量
     * @return 文件数量
     */
    public int getFileCount() {
        int count = 0;
        for (FileSystemComponent child : children) {
            if (child instanceof File) {
                count++;
            } else if (child instanceof Folder) {
                count += ((Folder) child).getFileCount();
            }
        }
        return count;
    }
    
    /**
     * 获取文件夹中的子文件夹数量
     * @return 子文件夹数量
     */
    public int getFolderCount() {
        int count = 0;
        for (FileSystemComponent child : children) {
            if (child instanceof Folder) {
                count += 1 + ((Folder) child).getFolderCount();
            }
        }
        return count;
    }
    
    // 格式化文件大小显示
    private String formatSize(long size) {
        if (size < 1024) {
            return size + " B";
        } else if (size < 1024 * 1024) {
            return String.format("%.1f KB", size / 1024.0);
        } else {
            return String.format("%.1f MB", size / (1024.0 * 1024.0));
        }
    }
    
    // Getter方法
    public String getName() { return name; }
    public List<FileSystemComponent> getChildren() { 
        return new ArrayList<>(children); 
    }
}

完整示例演示

让我们通过一个完整的文件系统示例来演示组合模式的实际应用:

java 复制代码
package com.YA33.designpattern.composite;

/**
 * 组合模式演示类 - 文件系统管理
 * 作者:YA33
 */
public class FileSystemDemo {
    public static void main(String[] args) {
        // 创建YA33项目根目录
        Folder ya33Project = new Folder("YA33项目");
        
        // 创建源代码文件夹
        Folder srcFolder = new Folder("src");
        Folder mainFolder = new Folder("main");
        Folder javaFolder = new Folder("java");
        Folder comFolder = new Folder("com");
        Folder ya33Folder = new Folder("ya33");
        
        // 创建Java源文件
        File mainApp = new File("MainApplication", 2048, "java");
        File userService = new File("UserService", 4096, "java");
        File productService = new File("ProductService", 5120, "java");
        File utils = new File("StringUtils", 1024, "java");
        
        // 构建源代码目录结构
        ya33Folder.add(mainApp);
        ya33Folder.add(userService);
        ya33Folder.add(productService);
        ya33Folder.add(utils);
        comFolder.add(ya33Folder);
        javaFolder.add(comFolder);
        mainFolder.add(javaFolder);
        srcFolder.add(mainFolder);
        
        // 创建资源文件夹
        Folder resourcesFolder = new Folder("resources");
        File config = new File("application", 512, "properties");
        File logConfig = new File("log4j2", 256, "xml");
        resourcesFolder.add(config);
        resourcesFolder.add(logConfig);
        srcFolder.add(resourcesFolder);
        
        // 创建测试文件夹
        Folder testFolder = new Folder("test");
        Folder testJavaFolder = new Folder("java");
        File mainTest = new File("MainApplicationTest", 1024, "java");
        testJavaFolder.add(mainTest);
        testFolder.add(testJavaFolder);
        srcFolder.add(testFolder);
        
        // 创建文档文件夹
        Folder docsFolder = new Folder("docs");
        File readme = new File("README", 2048, "md");
        File apiDoc = new File("API文档", 8192, "pdf");
        docsFolder.add(readme);
        docsFolder.add(apiDoc);
        
        // 创建配置文件
        File pom = new File("pom", 1024, "xml");
        File gitignore = new File("gitignore", 256, "");
        
        // 构建完整的项目结构
        ya33Project.add(srcFolder);
        ya33Project.add(docsFolder);
        ya33Project.add(pom);
        ya33Project.add(gitignore);
        
        // 显示完整的文件系统结构
        System.out.println("=== YA33项目文件系统结构 ===");
        ya33Project.display("");
        
        // 显示统计信息
        System.out.println("\n=== 项目统计信息 ===");
        System.out.println("项目总大小: " + formatSize(ya33Project.calculateSize()));
        System.out.println("文件总数: " + ya33Project.getFileCount());
        System.out.println("文件夹总数: " + ya33Project.getFolderCount());
        
        // 搜索功能演示
        System.out.println("\n=== 搜索包含 'Main' 的文件和文件夹 ===");
        List<FileSystemComponent> searchResults = ya33Project.search("Main");
        for (FileSystemComponent result : searchResults) {
            if (result instanceof File) {
                File file = (File) result;
                System.out.println("找到文件: " + file.getFileInfo());
            } else if (result instanceof Folder) {
                Folder folder = (Folder) result;
                System.out.println("找到文件夹: " + folder.getName());
            }
        }
        
        // 演示单个文件操作
        System.out.println("\n=== 单个文件操作演示 ===");
        readme.display("");
        System.out.println("文件大小: " + formatSize(readme.calculateSize()));
    }
    
    private static String formatSize(long size) {
        if (size < 1024) {
            return size + " B";
        } else if (size < 1024 * 1024) {
            return String.format("%.1f KB", size / 1024.0);
        } else {
            return String.format("%.1f MB", size / (1024.0 * 1024.0));
        }
    }
}

组合模式的应用场景

1. 文件系统管理

文件和文件夹的层次结构天然适合使用组合模式。

2. 图形用户界面

窗口包含面板,面板包含按钮、文本框等组件:

java 复制代码
// 伪代码示例
interface UIComponent {
    void render();
}

class Button implements UIComponent { /* 实现 */ }
class Panel implements UIComponent { /* 包含其他UI组件 */ }

3. 组织架构管理

公司-部门-员工的层次结构:

java 复制代码
// 伪代码示例
interface OrganizationUnit {
    int getPersonCount();
    double calculateCost();
}

4. 菜单系统

菜单包含子菜单和菜单项:

java 复制代码
// 伪代码示例
interface MenuComponent {
    void execute();
}

class MenuItem implements MenuComponent { /* 实现 */ }
class Menu implements MenuComponent { /* 包含其他菜单组件 */ }

组合模式的变体

透明组合模式

如上例所示,所有方法都在接口中定义,叶子节点对不支持的方法抛出异常。

优点 :客户端可以完全统一地对待所有组件
缺点:叶子节点需要实现一些无意义的方法

安全组合模式

只在接口中定义公共方法,复合节点特有的方法不在接口中声明。

优点 :避免了叶子节点实现无意义方法
缺点:客户端需要知道组件的具体类型

最佳实践建议

  1. 合理设计接口:仔细考虑哪些操作应该放在组件接口中
  2. 处理边界情况:为叶子节点的不支持操作提供合理的默认实现
  3. 考虑性能:对于大型层次结构,考虑缓存计算结果
  4. 保持一致性:确保所有组件的行为符合用户的预期

总结

组合模式通过将对象组织成树形结构,让我们能够以统一的方式处理单个对象和对象组合。这种模式特别适合表示层次结构,如文件系统、组织架构等。通过本文的详细示例,你可以看到组合模式在实际开发中的强大之处 - 它让复杂的层次结构变得简单而优雅。

组合模式的核心价值在于它提供了一种层次化对象的结构,让客户端代码可以一致地处理单个对象和组合对象。无论你是构建复杂的UI组件,还是管理多层次的数据结构,组合模式都是一个值得掌握的重要设计模式。

在实际项目中,合理运用组合模式可以大大提高代码的可维护性和扩展性,特别是在处理树形数据结构时,组合模式几乎是不二之选。

相关推荐
一枚码仔3 小时前
SpringBoot启动时执行自定义内容的5种方法
java·spring boot·后端
桦说编程3 小时前
如何在Java中实现支持随机访问的固定窗口队列
java·数据结构·后端
小白黑科技测评3 小时前
2025 年编程工具实测:零基础学习平台适配性全面解析!
java·开发语言·python
qwfys2003 小时前
实时Java规范(RTSJ):从理论到实践的实时系统编程范式
java·实时·java规范·rtsj
ejinxian3 小时前
Python 3.14 发布
java·开发语言·python
喜欢读源码的小白4 小时前
【Spring Boot + Spring Security】从入门到源码精通:藏经阁权限设计与过滤器链深度解析
java·开发语言·spring boot·spring security
BAGAE4 小时前
MQTT 与 HTTP 协议对比
java·linux·http·https·硬件工程
oak隔壁找我4 小时前
Spring框架中的跨域CORS配置详解
java·后端
摇滚侠4 小时前
Spring Boot3零基础教程,配置 GraalVM 环境,笔记88
java·spring boot·笔记