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
-
创建 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. 代码实现
-
组件接口
FileSystemComponent.java
javapackage 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; } } -
抽象组件类
FileSystemComponentImpl.java
javapackage 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)); } } } -
叶子节点
File.java
javapackage 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); } } -
组合节点
Directory.java
javapackage 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; } } -
客户端代码
FileSystemClient.java
javapackage 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(" "); } } -
测试类
FileSystemTest.java
javapackage 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组件树等
-