概述
组合模式是一种常用的结构型设计模式,它通过将对象组合成树形结构来表示"部分-整体"的层次关系。组合模式让客户端可以统一地处理单个对象和组合对象,无需关心自己处理的是单个对象还是整个组合结构。这种模式不仅提高了代码的复用性,还大大简化了复杂层次结构的处理逻辑。
什么是组合模式
组合模式(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 { /* 包含其他菜单组件 */ }
组合模式的变体
透明组合模式
如上例所示,所有方法都在接口中定义,叶子节点对不支持的方法抛出异常。
优点 :客户端可以完全统一地对待所有组件
缺点:叶子节点需要实现一些无意义的方法
安全组合模式
只在接口中定义公共方法,复合节点特有的方法不在接口中声明。
优点 :避免了叶子节点实现无意义方法
缺点:客户端需要知道组件的具体类型
最佳实践建议
- 合理设计接口:仔细考虑哪些操作应该放在组件接口中
- 处理边界情况:为叶子节点的不支持操作提供合理的默认实现
- 考虑性能:对于大型层次结构,考虑缓存计算结果
- 保持一致性:确保所有组件的行为符合用户的预期
总结
组合模式通过将对象组织成树形结构,让我们能够以统一的方式处理单个对象和对象组合。这种模式特别适合表示层次结构,如文件系统、组织架构等。通过本文的详细示例,你可以看到组合模式在实际开发中的强大之处 - 它让复杂的层次结构变得简单而优雅。
组合模式的核心价值在于它提供了一种层次化对象的结构,让客户端代码可以一致地处理单个对象和组合对象。无论你是构建复杂的UI组件,还是管理多层次的数据结构,组合模式都是一个值得掌握的重要设计模式。
在实际项目中,合理运用组合模式可以大大提高代码的可维护性和扩展性,特别是在处理树形数据结构时,组合模式几乎是不二之选。