结构型-组合模式

1. 项目结构

xml 复制代码
	composite-pattern-demo/
	├── src/
	│   ├── main/
	│   │   ├── java/
	│   │   │   └── com/
	│   │   │       └── demo/
	│   │   │           └── composite/
	│   │   │               ├── component/
	│   │   │               │   ├── FileSystemComponent.java
	│   │   │               │   └── FileSystemComponentImpl.java
	│   │   │               ├── composite/
	│   │   │               │   └── Directory.java
	│   │   │               ├── leaf/
	│   │   │               │   └── File.java
	│   │   │               └── client/
	│   │   │                   └── FileSystemClient.java
	│   │   └── resources/
	│   └── test/
	│       └── java/
	└── pom.xml
  1. 创建 Maven 项目

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <modelVersion>4.0.0</modelVersion>
        
        <groupId>com.demo</groupId>
        <artifactId>composite-pattern-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        
        <name>Composite Pattern Demo</name>
        <description>Composite Pattern implementation example</description>
        
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        
        <dependencies>
            <!-- JUnit for testing -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
        
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

2. 代码实现

  1. 组件接口

    FileSystemComponent.java

    java 复制代码
    package com.demo.composite.component;
    
    import java.util.List;
    
    /**
     * 文件系统组件接口 (Component)
     * 定义叶子和容器的共同操作
     */
    public interface FileSystemComponent {
        
        /**
         * 获取组件名称
         */
        String getName();
        
        /**
         * 获取组件大小
         */
        long getSize();
        
        /**
         * 显示组件信息
         * @param indent 缩进字符串
         */
        void display(String indent);
        
        /**
         * 添加子组件 (仅容器需要实现)
         * @param component 子组件
         */
        default void add(FileSystemComponent component) {
            throw new UnsupportedOperationException("不支持添加操作");
        }
        
        /**
         * 移除子组件 (仅容器需要实现)
         * @param component 子组件
         */
        default void remove(FileSystemComponent component) {
            throw new UnsupportedOperationException("不支持移除操作");
        }
        
        /**
         * 获取子组件列表 (仅容器需要实现)
         * @return 子组件列表
         */
        default List<FileSystemComponent> getChildren() {
            throw new UnsupportedOperationException("不支持获取子组件操作");
        }
        
        /**
         * 是否是叶子节点
         * @return true如果是叶子节点
         */
        default boolean isLeaf() {
            return true;
        }
    }
  2. 抽象组件类

    FileSystemComponentImpl.java

    java 复制代码
    package com.demo.composite.component;
    
    import java.util.Objects;
    
    /**
     * 抽象组件类,实现公共方法
     */
    public abstract class FileSystemComponentImpl implements FileSystemComponent {
        
        protected String name;
        
        public FileSystemComponentImpl(String name) {
            this.name = Objects.requireNonNull(name, "名称不能为空");
        }
        
        @Override
        public String getName() {
            return name;
        }
        
        @Override
        public void display(String indent) {
            System.out.println(indent + toString());
        }
        
        @Override
        public String toString() {
            return String.format("%s [%s]", name, formatSize(getSize()));
        }
        
        protected 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));
            }
        }
    }
  3. 叶子节点

    File.java

    java 复制代码
    package com.demo.composite.leaf;
    
    import com.demo.composite.component.FileSystemComponentImpl;
    
    /**
     * 文件类 (Leaf)
     * 表示叶子节点,没有子组件
     */
    public class File extends FileSystemComponentImpl {
        
        private final long size;
        private final String extension;
        
        public File(String name, long size) {
            super(name);
            this.size = Math.max(size, 0);
            this.extension = extractExtension(name);
        }
        
        private String extractExtension(String fileName) {
            int dotIndex = fileName.lastIndexOf('.');
            return dotIndex > 0 ? fileName.substring(dotIndex + 1) : "";
        }
        
        @Override
        public long getSize() {
            return size;
        }
        
        public String getExtension() {
            return extension;
        }
        
        @Override
        public String toString() {
            return String.format("文件: %s (扩展名: %s)", name, 
                    extension.isEmpty() ? "无" : extension);
        }
    }
  4. 组合节点

    Directory.java

    java 复制代码
    package com.demo.composite.composite;
    
    import com.demo.composite.component.FileSystemComponent;
    import com.demo.composite.component.FileSystemComponentImpl;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * 目录类 (Composite)
     * 表示容器节点,可以包含其他组件
     */
    public class Directory extends FileSystemComponentImpl {
        
        private final List<FileSystemComponent> children;
        
        public Directory(String name) {
            super(name);
            this.children = new ArrayList<>();
        }
        
        @Override
        public long getSize() {
            // 递归计算所有子组件的大小总和
            return children.stream()
                    .mapToLong(FileSystemComponent::getSize)
                    .sum();
        }
        
        @Override
        public boolean isLeaf() {
            return false;
        }
        
        @Override
        public void add(FileSystemComponent component) {
            if (component != null && component != this) {
                children.add(component);
            }
        }
        
        @Override
        public void remove(FileSystemComponent component) {
            children.remove(component);
        }
        
        @Override
        public List<FileSystemComponent> getChildren() {
            return new ArrayList<>(children);
        }
        
        @Override
        public void display(String indent) {
            System.out.println(indent + "目录: " + name + " [" + formatSize(getSize()) + "]");
            
            // 递归显示所有子组件
            String childIndent = indent + "  ";
            for (FileSystemComponent child : children) {
                child.display(childIndent);
            }
        }
        
        /**
         * 按类型筛选文件
         * @param extension 文件扩展名
         * @return 匹配的文件列表
         */
        public List<File> findFilesByExtension(String extension) {
            List<File> result = new ArrayList<>();
            findFilesByExtensionRecursive(this, extension, result);
            return result;
        }
        
        private void findFilesByExtensionRecursive(FileSystemComponent component, 
                                                   String extension, 
                                                   List<File> result) {
            if (component instanceof File) {
                File file = (File) component;
                if (file.getExtension().equalsIgnoreCase(extension)) {
                    result.add(file);
                }
            } else if (component instanceof Directory) {
                Directory directory = (Directory) component;
                for (FileSystemComponent child : directory.getChildren()) {
                    findFilesByExtensionRecursive(child, extension, result);
                }
            }
        }
        
        /**
         * 获取目录下的所有文件
         * @return 文件列表
         */
        public List<File> getAllFiles() {
            List<File> result = new ArrayList<>();
            getAllFilesRecursive(this, result);
            return result;
        }
        
        private void getAllFilesRecursive(FileSystemComponent component, List<File> result) {
            if (component instanceof File) {
                result.add((File) component);
            } else if (component instanceof Directory) {
                Directory directory = (Directory) component;
                for (FileSystemComponent child : directory.getChildren()) {
                    getAllFilesRecursive(child, result);
                }
            }
        }
        
        @Override
        public String toString() {
            int fileCount = (int) getAllFiles().size();
            int dirCount = countDirectories(this) - 1; // 减去自身
            return String.format("目录: %s (%d 个文件, %d 个子目录)", 
                    name, fileCount, dirCount);
        }
        
        private int countDirectories(FileSystemComponent component) {
            if (component instanceof File) {
                return 0;
            } else if (component instanceof Directory) {
                Directory directory = (Directory) component;
                int count = 1; // 当前目录
                for (FileSystemComponent child : directory.getChildren()) {
                    count += countDirectories(child);
                }
                return count;
            }
            return 0;
        }
    }
  5. 客户端代码

    FileSystemClient.java

    java 复制代码
    package com.demo.composite.client;
    
    import com.demo.composite.composite.Directory;
    import com.demo.composite.leaf.File;
    
    /**
     * 客户端演示组合模式的使用
     */
    public class FileSystemClient {
        
        public static void main(String[] args) {
            System.out.println("=== 组合模式演示:文件系统 ===");
            
            // 1. 创建文件
            File file1 = new File("document.txt", 1500);
            File file2 = new File("image.jpg", 2500000);
            File file3 = new File("report.pdf", 1800000);
            File file4 = new File("code.java", 8000);
            File file5 = new File("data.xml", 12000);
            File file6 = new File("notes.txt", 500);
            
            // 2. 创建目录结构
            Directory root = new Directory("根目录");
            
            Directory documents = new Directory("文档");
            Directory images = new Directory("图片");
            Directory sourceCode = new Directory("源代码");
            
            Directory projectA = new Directory("项目A");
            Directory projectB = new Directory("项目B");
            
            // 3. 构建目录树
            root.add(documents);
            root.add(images);
            root.add(sourceCode);
            
            documents.add(file1);
            documents.add(file3);
            documents.add(file6);
            
            images.add(file2);
            
            sourceCode.add(projectA);
            sourceCode.add(projectB);
            sourceCode.add(file4);
            
            projectA.add(new File("Main.java", 3000));
            projectA.add(new File("Utils.java", 2000));
            
            projectB.add(new File("App.java", 4000));
            projectB.add(new File("Config.java", 1500));
            projectB.add(file5);
            
            // 4. 显示整个文件系统
            System.out.println("\n--- 完整的文件系统结构 ---");
            root.display("");
            
            // 5. 显示根目录信息
            System.out.println("\n--- 根目录信息 ---");
            System.out.println(root);
            System.out.println("总大小: " + root.getSize() + " bytes");
            
            // 6. 查找特定类型的文件
            System.out.println("\n--- 查找所有 .txt 文件 ---");
            root.findFilesByExtension("txt").forEach(file -> {
                System.out.println("  - " + file.getName() + " (" + file.getSize() + " bytes)");
            });
            
            // 7. 获取所有文件
            System.out.println("\n--- 所有文件列表 ---");
            root.getAllFiles().forEach(file -> {
                System.out.println("  - " + file.getName() + 
                        " [" + file.getExtension() + ", " + file.getSize() + " bytes]");
            });
            
            // 8. 操作示例
            System.out.println("\n--- 操作示例 ---");
            System.out.println("文档目录是否是叶子节点: " + documents.isLeaf());
            System.out.println("文件是否是叶子节点: " + file1.isLeaf());
            System.out.println("文档目录子组件数量: " + documents.getChildren().size());
            
            // 9. 添加新文件
            System.out.println("\n--- 添加新文件到文档目录 ---");
            File newFile = new File("new_document.docx", 450000);
            documents.add(newFile);
            documents.display("  ");
            
            // 10. 移除文件
            System.out.println("\n--- 移除文件后 ---");
            documents.remove(file6);
            documents.display("  ");
        }
    }
  6. 测试类

    FileSystemTest.java

    java 复制代码
    package com.demo.composite;
    
    import com.demo.composite.composite.Directory;
    import com.demo.composite.leaf.File;
    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class FileSystemTest {
        
        @Test
        public void testFileCreation() {
            File file = new File("test.txt", 1000);
            
            assertEquals("test.txt", file.getName());
            assertEquals(1000, file.getSize());
            assertEquals("txt", file.getExtension());
            assertTrue(file.isLeaf());
        }
        
        @Test
        public void testDirectoryCreation() {
            Directory dir = new Directory("testDir");
            
            assertEquals("testDir", dir.getName());
            assertEquals(0, dir.getSize());
            assertFalse(dir.isLeaf());
            assertTrue(dir.getChildren().isEmpty());
        }
        
        @Test
        public void testCompositeStructure() {
            // 创建文件
            File file1 = new File("file1.txt", 100);
            File file2 = new File("file2.jpg", 200);
            
            // 创建目录
            Directory root = new Directory("root");
            Directory subDir = new Directory("subDir");
            
            // 构建结构
            root.add(subDir);
            root.add(file1);
            subDir.add(file2);
            
            // 验证结构
            assertEquals(2, root.getChildren().size());
            assertEquals(1, subDir.getChildren().size());
            assertEquals(300, root.getSize()); // 100 + 200
            
            // 验证文件查找
            assertEquals(1, root.findFilesByExtension("txt").size());
            assertEquals(1, root.findFilesByExtension("jpg").size());
            assertEquals(0, root.findFilesByExtension("pdf").size());
        }
        
        @Test
        public void testRemoveComponent() {
            Directory dir = new Directory("dir");
            File file = new File("test.txt", 100);
            
            dir.add(file);
            assertEquals(1, dir.getChildren().size());
            
            dir.remove(file);
            assertEquals(0, dir.getChildren().size());
            assertEquals(0, dir.getSize());
        }
        
        @Test(expected = UnsupportedOperationException.class)
        public void testFileAddThrowsException() {
            File file = new File("test.txt", 100);
            File anotherFile = new File("another.txt", 200);
            
            // 应该抛出异常,因为文件是叶子节点
            file.add(anotherFile);
        }
        
        @Test
        public void testEmptyFileExtension() {
            File file1 = new File("README", 500);
            File file2 = new File("test.file.txt", 300);
            
            assertEquals("", file1.getExtension());
            assertEquals("txt", file2.getExtension());
        }
    }

3. 构建和运行

编译项目:

bash 复制代码
mvn clean compile

运行客户端:

bash 复制代码
mvn exec:java -Dexec.mainClass="com.demo.composite.client.FileSystemClient"

运行测试:

bash 复制代码
mvn test

4. 核心概念

  • 模式结构:

    • Component(抽象组件):FileSystemComponent - 定义叶子和容器的共同接口

    • Leaf(叶子):File - 表示叶子节点对象,没有子节点

    • Composite(组合):Directory - 表示容器节点,可以包含子组件

    • Client(客户端):FileSystemClient - 通过组件接口操作组合结构

  • 优点:

    • 一致性处理:客户端可以统一处理单个对象和组合对象

    • 灵活的结构:可以方便地添加新的组件类型

    • 简化客户端代码:客户端不需要判断处理的是叶子还是容器

    • 递归组合:支持递归结构,便于处理树形结构

  • 适用场景:

    • 需要表示对象的部分-整体层次结构

    • 希望客户端忽略组合对象与单个对象的差异

    • 结构具有递归性质,如文件系统、组织架构、UI组件树等

相关推荐
@淡 定2 小时前
缓存原理详解
java·spring·缓存
就叫飞六吧2 小时前
基于spring web实现简单分片上传demo
java·前端·spring
程序猿零零漆2 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(三)Bean的依赖注入配置、Spring的其它配置标签
java·学习·spring
程序猿零零漆3 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(二)基于Xml方式Bean的配置
xml·spring
东东的脑洞3 小时前
【面试突击】Spring Security + OAuth2 密码模式实战:Gateway 作为网关与资源服务器,Auth 作为认证服务器(完整认证链路解析)
spring·面试·gateway
L1624764 小时前
Redis 删除缓存全场景操作手册(详细版)
redis·spring·缓存
廋到被风吹走13 小时前
【Spring】DispatcherServlet解析
java·后端·spring
廋到被风吹走13 小时前
【Spring】PlatformTransactionManager详解
java·spring·wpf
それども15 小时前
Spring Bean 的name可以相同吗
java·后端·spring