2025/9/26日 每日总结 设计模式实践:透明组合模式之文件系统浏览案例解析

设计模式实践:透明组合模式之文件系统浏览案例解析

在软件开发中,经常需要处理类似“文件-文件夹”这样的树形结构数据,其中既有单个对象(文件),也有组合对象(文件夹)。组合模式通过将对象组合成树形结构,实现了“部分-整体”的层次关系,让客户端可以统一处理单个对象和组合对象。本文将通过文件系统浏览的经典案例,详细拆解透明组合模式的实现逻辑与应用场景。

一、实验背景与需求

本次实践的核心需求是用透明组合模式实现文件系统浏览功能,具体要求如下:

  1. 支持两种核心节点类型:文件夹(组合对象)和文件(叶子对象),文件类型包括文本文件、图像文件、视频文件

  2. 文件夹可包含多个子节点(文件或子文件夹),支持添加、删除子节点操作

  3. 文件作为叶子节点,不支持添加、删除操作(需提示不支持)

  4. 支持递归显示文件系统的树形结构

  5. 无需实现文件的实际执行功能,仅需提供操作提示

二、透明组合模式核心结构

透明组合模式的关键在于抽象组件类声明了所有管理子组件的方法(包括添加、删除),无论是组合对象还是叶子对象都实现该接口。本次案例的结构设计如下:

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方法无实际意义,仅为满足接口契约

    六、适用场景总结

    透明组合模式特别适合以下场景:

  • 需处理树形结构数据,存在“部分-整体”层次关系(如文件系统、组织架构、菜单系统)

  • 客户端希望统一处理单个对象和组合对象,无需区分节点类型

  • 系统需支持动态添加、删除节点,且节点类型可能扩展
    通过本次文件系统浏览的实践案例,深刻体会到组合模式在处理树形结构时的强大优势。它将复杂的层级关系封装在组件内部,让客户端可以像操作单个对象一样操作整个树形结构,大大简化了代码逻辑。在实际开发中,当遇到菜单导航、部门组织架构、文件管理等场景时,组合模式都是一个非常理想的解决方案。

posted @ 2025-12-29 14:31  Moonbeamsc  阅读(14)  评论(0)    收藏  举报
返回顶端