组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树形结构以表示"部分-整体"的层次关系。这种模式使得客户端对单个对象和组合对象的使用具有一致性,无需区分处理单个对象还是整个对象树。

组合模式的核心角色

  1. 抽象组件(Component):定义叶子节点和组合节点的共同接口
  2. 叶子节点(Leaf):表示树形结构中的叶子对象,没有子节点
  3. 组合节点(Composite):表示包含子节点的复合对象,可以是叶子节点或其他组合节点

组合模式的实现示例

下面以"文件系统"为例展示组合模式的实现,文件系统由文件(叶子节点)和文件夹(组合节点)组成,文件夹可以包含文件或其他文件夹:

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 抽象组件:文件系统节点
class FileSystemComponent {
protected:
    std::string name;

public:
    FileSystemComponent(const std::string& componentName) : name(componentName) {}
    virtual ~FileSystemComponent() = default;

    // 纯虚函数:显示节点信息
    virtual void display(int depth = 0) const = 0;
    
    // 虚函数:添加子节点(默认不实现,叶子节点会忽略)
    virtual void add(std::shared_ptr<FileSystemComponent> component) {
        // 空实现,叶子节点不支持添加操作
    }
    
    // 虚函数:移除子节点(默认不实现)
    virtual void remove(const std::string& componentName) {
        // 空实现,叶子节点不支持移除操作
    }
};

// 叶子节点:文件
class File : public FileSystemComponent {
private:
    int size;  // 文件大小(KB)

public:
    File(const std::string& fileName, int fileSize)
        : FileSystemComponent(fileName), size(fileSize) {}

    // 显示文件信息
    void display(int depth = 0) const override {
        std::string indent(depth, '-');
        std::cout << indent << "文件: " << name << " (" << size << "KB)" << std::endl;
    }

    // 文件不支持添加子节点,重写为空实现
    void add(std::shared_ptr<FileSystemComponent> component) override {}
    
    // 文件不支持移除子节点
    void remove(const std::string& componentName) override {}
};

// 组合节点:文件夹
class Folder : public FileSystemComponent {
private:
    // 存储子节点(可以是文件或其他文件夹)
    std::vector<std::shared_ptr<FileSystemComponent>> children;

public:
    Folder(const std::string& folderName) : FileSystemComponent(folderName) {}

    // 显示文件夹信息及所有子节点
    void display(int depth = 0) const override {
        std::string indent(depth, '-');
        std::cout << indent << "文件夹: " << name << std::endl;
        
        // 递归显示所有子节点,深度+1
        for (const auto& child : children) {
            child->display(depth + 2);
        }
    }

    // 添加子节点
    void add(std::shared_ptr<FileSystemComponent> component) override {
        children.push_back(component);
    }

    // 移除子节点
    void remove(const std::string& componentName) override {
        for (auto it = children.begin(); it != children.end(); ++it) {
            if ((*it)->name == componentName) {
                children.erase(it);
                std::cout << "已移除: " << componentName << std::endl;
                return;
            }
        }
        std::cout << "未找到: " << componentName << std::endl;
    }
};

// 客户端使用
int main() {
    // 创建文件
    auto readme = std::make_shared<File>("readme.txt", 10);
    auto image = std::make_shared<File>("photo.jpg", 2048);
    auto data = std::make_shared<File>("data.csv", 512);
    
    // 创建子文件夹
    auto docsFolder = std::make_shared<Folder>("文档");
    docsFolder->add(std::make_shared<File>("报告.docx", 1024));
    docsFolder->add(std::make_shared<File>("简历.pdf", 2048));
    
    // 创建主文件夹并添加内容
    auto mainFolder = std::make_shared<Folder>("我的文件");
    mainFolder->add(readme);
    mainFolder->add(image);
    mainFolder->add(docsFolder);
    
    // 显示整个文件系统结构
    std::cout << "=== 初始文件系统结构 ===" << std::endl;
    mainFolder->display();
    
    // 移除一个文件并添加新文件
    mainFolder->remove("photo.jpg");
    mainFolder->add(data);
    
    // 显示更新后的结构
    std::cout << "\n=== 更新后的文件系统结构 ===" << std::endl;
    mainFolder->display();
    
    return 0;
}

组合模式的工作原理

  1. 抽象组件定义了叶子节点和组合节点的共同接口,确保客户端可以一致地处理它们
  2. 叶子节点是树形结构的最底层,不包含子节点,实现基本功能
  3. 组合节点包含子节点集合,可以添加、移除子节点,并递归处理子节点的操作
  4. 客户端通过抽象组件接口操作整个树形结构,无需区分处理的是单个对象还是组合对象

组合模式的两种实现方式

  • 透明模式:抽象组件声明所有用于管理子节点的方法(如示例中所示),叶子节点空实现这些方法
  • 安全模式:抽象组件只声明基本操作,管理子节点的方法在组合节点中声明,客户端需要区分叶子和组合节点

透明模式更符合组合模式的初衷,使得客户端可以统一处理所有组件。

组合模式的应用场景

  1. 表示"部分-整体"层次结构的场景(如文件系统、组织机构)
  2. 需要客户端统一处理单个对象和组合对象的场景
  3. 维护和展示树形结构的场景(如UI组件树、XML/JSON文档结构)
  4. 希望通过递归方式处理树形结构中所有节点的场景

组合模式的优缺点

优点

  • 客户端可以一致地处理单个对象和组合对象,简化了客户端代码
  • 便于扩展新的组件类型,符合开放-封闭原则
  • 可以通过递归方式方便地处理整个树形结构
  • 清晰地表示了对象的层次结构

缺点

  • 设计较复杂,需要合理抽象组件接口
  • 可能会限制叶子节点的特性,因为它们需要实现组合节点的接口
  • 对于复杂的树形结构,可能导致性能问题

组合模式在GUI库中应用广泛,例如Qt的QWidget体系,每个窗口组件既可以是简单控件(叶子),也可以是包含其他控件的容器(组合),客户端可以统一处理所有组件。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐