2025/9/26日 每日总结 设计模式实践:透明组合模式之文件系统浏览案例解析
设计模式实践:透明组合模式之文件系统浏览案例解析
在软件开发中,经常需要处理类似“文件-文件夹”这样的树形结构数据,其中既有单个对象(文件),也有组合对象(文件夹)。组合模式通过将对象组合成树形结构,实现了“部分-整体”的层次关系,让客户端可以统一处理单个对象和组合对象。本文将通过文件系统浏览的经典案例,详细拆解透明组合模式的实现逻辑与应用场景。
一、实验背景与需求
本次实践的核心需求是用透明组合模式实现文件系统浏览功能,具体要求如下:
-
支持两种核心节点类型:文件夹(组合对象)和文件(叶子对象),文件类型包括文本文件、图像文件、视频文件
-
文件夹可包含多个子节点(文件或子文件夹),支持添加、删除子节点操作
-
文件作为叶子节点,不支持添加、删除操作(需提示不支持)
-
支持递归显示文件系统的树形结构
-
无需实现文件的实际执行功能,仅需提供操作提示
二、透明组合模式核心结构
透明组合模式的关键在于抽象组件类声明了所有管理子组件的方法(包括添加、删除),无论是组合对象还是叶子对象都实现该接口。本次案例的结构设计如下:
1. 核心组件划分
| 组件类型 | 具体实现 | 职责描述 |
|---|---|---|
| 抽象组件 | AbstractFile | 声明所有组件的通用方法(add、remove、display),定义文件/文件夹的公共属性(文件名) |
| 组合组件 | Folder | 文件夹类,可包含子组件,实现add、remove、display的完整逻辑,维护子组件列表 |
| 叶子组件 | TextFile、ImageFile、VideoFile | 各类文件类,实现display方法,add/remove方法抛出不支持异常 |
2. 类图结构
┌─────────────────┐
│ AbstractFile │ ← 抽象组件(透明模式核心)
├─────────────────┤
│ - filename: String │
├─────────────────┤
│ + AbstractFile(filename) │
│ + add(ele: AbstractFile): void │ ← 所有组件都需实现的方法
│ + remove(ele: AbstractFile): void │ ← 所有组件都需实现的方法
│ + display(): void │ ← 显示节点信息
│ + getFilename(): String │
└─────────────────┘
▲
│
┌───────────────┬───────────────┬───────────────┬───────────────┐
│ Folder │ TextFile │ ImageFile │ VideoFile │
├───────────────┤───────────────┤───────────────┤───────────────┤
│ - fileList: │ │ │ │
│ List<AbstractFile> │ │ │
├───────────────┤───────────────┤───────────────┤───────────────┤
│ + Folder(filename) │ + TextFile(filename) │ + ImageFile(filename) │ + VideoFile(filename) │
│ + add(ele): void │ + add(ele): void │ + add(ele): void │ + add(ele): void │
│ + remove(ele): void │ + remove(ele): void │ + remove(ele): void │ + remove(ele): void │
│ + display(): void │ + display(): void │ + display(): void │ + display(): void │
└───────────────┘───────────────┘───────────────┘───────────────┘
组合组件(文件夹) 叶子组件(各类文件)
三、完整实现代码
1. 抽象组件类:AbstractFile.java
/**
* 抽象文件类 - 透明组合模式的核心抽象组件
* 声明所有管理子组件的方法,确保客户端可以统一处理所有组件
*/
public abstract class AbstractFile {
protected String filename;
// 构造方法:初始化文件名
public AbstractFile(String filename) {
this.filename = filename;
}
// 抽象方法:添加子组件(文件夹支持,文件不支持)
public abstract void add(AbstractFile ele);
// 抽象方法:移除子组件(文件夹支持,文件不支持)
public abstract void remove(AbstractFile ele);
// 抽象方法:显示组件信息(递归显示树形结构)
public abstract void display();
// 获取文件名
public String getFilename() {
return filename;
}
}
### 2. 组合组件类:Folder.java
```java
import java.util.ArrayList;
import java.util.List;
/**
* 文件夹类 - 组合组件,可包含子文件或子文件夹
*/
public class Folder extends AbstractFile {
// 维护子组件列表
private List<AbstractFile> fileList = new ArrayList<>();
public Folder(String filename) {
super(filename);
}
// 添加子组件(文件或文件夹)
@Override
public void add(AbstractFile ele) {
fileList.add(ele);
System.out.println("添加文件到文件夹: " + filename);
}
// 移除子组件(文件或文件夹)
@Override
public void remove(AbstractFile ele) {
if (fileList.remove(ele)) {
System.out.println("从文件夹移除文件: " + filename);
} else {
System.out.println("文件不存在于文件夹: " + filename);
}
}
// 递归显示文件夹内容(包含所有子组件)
@Override
public void display() {
System.out.println("文件夹: " + filename);
System.out.println("包含内容:");
for (AbstractFile file : fileList) {
file.display(); // 递归调用子组件的display方法
}
System.out.println("文件夹结束: " + filename);
}
// 获取文件夹中子组件数量
public int getFileCount() {
return fileList.size();
}
}
3. 叶子组件类(各类文件)
文本文件:TextFile.java
/**
* 文本文件类 - 叶子组件,不支持添加/移除操作
*/
public class TextFile extends AbstractFile {
public TextFile(String filename) {
super(filename);
}
// 叶子节点不支持添加操作,抛出异常
@Override
public void add(AbstractFile ele) {
throw new UnsupportedOperationException("文本文件不支持添加操作");
}
// 叶子节点不支持移除操作,抛出异常
@Override
public void remove(AbstractFile ele) {
throw new UnsupportedOperationException("文本文件不支持移除操作");
}
// 显示文本文件信息
@Override
public void display() {
System.out.println("文本文件: " + filename);
}
}
图像文件:ImageFile.java
/**
* 图像文件类 - 叶子组件,不支持添加/移除操作
*/
public class ImageFile extends AbstractFile {
public ImageFile(String filename) {
super(filename);
}
@Override
public void add(AbstractFile ele) {
throw new UnsupportedOperationException("图像文件不支持添加操作");
}
@Override
public void remove(AbstractFile ele) {
throw new UnsupportedOperationException("图像文件不支持移除操作");
}
@Override
public void display() {
System.out.println("图像文件: " + filename);
}
}
视频文件:VideoFile.java
/**
* 视频文件类 - 叶子组件,不支持添加/移除操作
*/
public class VideoFile extends AbstractFile {
public VideoFile(String filename) {
super(filename);
}
@Override
public void add(AbstractFile ele) {
throw new UnsupportedOperationException("视频文件不支持添加操作");
}
@Override
public void remove(AbstractFile ele) {
throw new UnsupportedOperationException("视频文件不支持移除操作");
}
@Override
public void display() {
System.out.println("视频文件: " + filename);
}
}
4. 客户端测试类(C++版本示例)
#include <iostream>
#include "Folder.h"
#include "ImageFile.h"
#include "TextFile.h"
#include "VideoFile.h"
/**
* 客户端代码 - 演示文件系统的创建、浏览和操作
*/
int main() {
// 1. 创建叶子节点(各类文件)
AbstractFile* landscapeImg = new ImageFile("风景照.jpg");
AbstractFile* diaryTxt = new TextFile("日记.txt");
AbstractFile* travelVideo = new VideoFile("旅行视频.mp4");
AbstractFile* selfieImg = new ImageFile("自拍照.png");
// 2. 创建组合节点(文件夹)
Folder* picturesFolder = new Folder("图片");
Folder* documentsFolder = new Folder("文档");
Folder* videosFolder = new Folder("视频");
Folder* rootFolder = new Folder("我的文件");
// 3. 构建树形文件结构
picturesFolder->add(landscapeImg);
picturesFolder->add(selfieImg);
documentsFolder->add(diaryTxt);
videosFolder->add(travelVideo);
rootFolder->add(picturesFolder);
rootFolder->add(documentsFolder);
rootFolder->add(videosFolder);
// 4. 显示完整文件系统结构
std::cout << "=== 文件系统结构 ===" << std::endl;
rootFolder->display();
// 5. 演示删除操作
std::cout << "\n=== 删除操作演示 ===" << std::endl;
rootFolder->remove(documentsFolder);
// 6. 显示删除后的文件系统结构
std::cout << "\n=== 删除后的文件系统结构 ===" << std::endl;
rootFolder->display();
// 7. 演示叶子节点的不支持操作
std::cout << "\n=== 叶子节点操作演示 ===" << std::endl;
try {
landscapeImg->add(diaryTxt); // 尝试给图片文件添加子节点
} catch (const std::exception& e) {
std::cout << "预期中的异常: " << e.what() << std::endl;
}
// 8. 清理内存(实际项目中可使用智能指针优化)
delete rootFolder;
delete documentsFolder;
delete picturesFolder;
delete videosFolder;
delete landscapeImg;
delete diaryTxt;
delete travelVideo;
delete selfieImg;
return 0;
}
四、运行结果
=== 文件系统结构 ===
文件夹: 我的文件
包含内容:
文件夹: 图片
包含内容:
图像文件: 风景照.jpg
图像文件: 自拍照.png
文件夹结束: 图片
文件夹: 文档
包含内容:
文本文件: 日记.txt
文件夹结束: 文档
文件夹: 视频
包含内容:
视频文件: 旅行视频.mp4
文件夹结束: 视频
文件夹结束: 我的文件
=== 删除操作演示 ===
从文件夹移除文件: 我的文件
=== 删除后的文件系统结构 ===
文件夹: 我的文件
包含内容:
文件夹: 图片
包含内容:
图像文件: 风景照.jpg
图像文件: 自拍照.png
文件夹结束: 图片
文件夹: 视频
包含内容:
视频文件: 旅行视频.mp4
文件夹结束: 视频
文件夹结束: 我的文件
=== 叶子节点操作演示 ===
预期中的异常: 图像文件不支持添加操作
五、透明组合模式核心特性与优缺点
1. 核心特性
-
透明性:客户端无需区分组合对象和叶子对象,统一通过AbstractFile接口操作所有节点
-
一致性:单个对象和组合对象的处理方式一致,简化了客户端代码
-
树形结构支持:天然支持递归遍历,轻松实现复杂的层级结构展示
2. 优点
-
客户端代码简洁:无需判断节点类型,直接调用统一接口
-
扩展性强:新增文件类型或文件夹类型时,只需继承AbstractFile并实现对应方法,符合开闭原则
-
层次结构清晰:通过组合模式构建的树形结构,便于维护和扩展
3. 缺点
-
安全性不足:叶子节点实现了不支持的add/remove方法,可能导致客户端误操作(需通过异常提示避免)
-
接口冗余:叶子节点的add/remove方法无实际意义,仅为满足接口契约
六、适用场景总结
透明组合模式特别适合以下场景:
-
需处理树形结构数据,存在“部分-整体”层次关系(如文件系统、组织架构、菜单系统)
-
客户端希望统一处理单个对象和组合对象,无需区分节点类型
-
系统需支持动态添加、删除节点,且节点类型可能扩展
通过本次文件系统浏览的实践案例,深刻体会到组合模式在处理树形结构时的强大优势。它将复杂的层级关系封装在组件内部,让客户端可以像操作单个对象一样操作整个树形结构,大大简化了代码逻辑。在实际开发中,当遇到菜单导航、部门组织架构、文件管理等场景时,组合模式都是一个非常理想的解决方案。

浙公网安备 33010602011771号