组合模式及优化

组合模式是一种结构型设计模式,其核心思想是将对象组合成树形结构,以表示"部分-整体"的层次关系,使得用户对单个对象和组合对象的使用具有一致性。

一、介绍

核心角色

组合模式包含以下3个关键角色:

  1. 抽象组件(Component)
    定义单个对象和组合对象的共同接口,声明所有操作(如添加、删除子节点、获取子节点等)。
  2. 叶子节点(Leaf)
    表示树形结构中的"单个对象",没有子节点。实现抽象组件的接口,但不支持"添加/删除子节点"等操作(通常抛出异常)。
  3. 复合节点(Composite)
    表示树形结构中的"组合对象",可以包含子节点(叶子节点或其他复合节点)。实现抽象组件的接口,并重写"添加/删除子节点"等操作,通过管理子节点集合实现功能。
优点
  1. 一致性操作:用户无需区分单个对象和组合对象,统一调用接口即可处理整个树形结构。
  2. 扩展性强:新增叶子节点或复合节点时,无需修改现有代码(符合开闭原则)。
  3. 简化客户端逻辑:客户端无需编写复杂的判断逻辑(如"是否为组合对象"),直接递归处理即可。
  4. 清晰表示层次关系:通过树形结构直观体现"部分-整体"关系,便于理解和维护。
适用场景

当需要处理 具有"部分-整体"层次关系的对象结构,且希望用户忽略单个对象和组合对象的差异时,适合使用组合模式。典型场景包括:

  • 树形结构数据:如文件系统(文件夹与文件)、组织机构(部门与员工)、XML/JSON节点等。
  • UI组件:如按钮(叶子)和面板(复合,包含按钮/其他面板)。
  • 图形绘制:如基本图形(直线、圆)和组合图形(由多个基本图形组成)。

二、实现

以文件系统的为例,使用组合模式表示文件和文件夹的层次结构:

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <memory>

// 抽象组件类:定义文件和文件夹的共同接口
class FileSystemComponent {
protected:
    std::string name_;

public:
    explicit FileSystemComponent(std::string name) : name_(std::move(name)) {}
    virtual ~FileSystemComponent() = default;

    // 获取名称
    std::string getName() const {
        return name_;
    }

    // 纯虚函数:显示组件信息(声明为纯虚函数,使该类成为抽象类)
    virtual void display(int depth = 0) const = 0;

    // 虚函数:添加子组件(默认不实现,由容器类重写)
    virtual void add(std::shared_ptr<FileSystemComponent> component) {
        throw std::runtime_error("不支持添加操作");
    }

    // 虚函数:移除子组件(默认不实现,由容器类重写)
    virtual void remove(const std::string& name) {
        throw std::runtime_error("不支持移除操作");
    }

    // 虚函数:获取子组件(默认不实现,由容器类重写)
    virtual std::shared_ptr<FileSystemComponent> getChild(const std::string& name) {
        throw std::runtime_error("不支持获取子组件操作");
    }
};

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

public:
    File(std::string name, int size) : FileSystemComponent(std::move(name)), size_(size) {}

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

// 容器节点:文件夹
class Folder : public FileSystemComponent {
private:
    std::vector<std::shared_ptr<FileSystemComponent>> children_;

public:
    explicit Folder(std::string name) : FileSystemComponent(std::move(name)) {}

    // 添加子组件(文件或文件夹)
    void add(std::shared_ptr<FileSystemComponent> component) override {
        children_.push_back(std::move(component));
    }

    // 移除子组件
    void remove(const std::string& name) override {
        auto it = std::remove_if(children_.begin(), children_.end(),
            [&name](const std::shared_ptr<FileSystemComponent>& comp) {
                return comp->getName() == name;
            });
        if (it != children_.end()) {
            children_.erase(it, children_.end());
        }
    }

    // 获取子组件
    std::shared_ptr<FileSystemComponent> getChild(const std::string& name) override {
        for (const auto& child : children_) {
            if (child->getName() == name) {
                return child;
            }
        }
        return nullptr;
    }

    // 显示文件夹信息及所有子组件
    void display(int depth = 0) const override {
        std::string indent(depth, '-');
        std::cout << indent << "文件夹: " << name_ << " (包含 " << children_.size() << " 个项目)" << std::endl;

        // 递归显示子组件,深度+1
        for (const auto& child : children_) {
            child->display(depth + 2);
        }
    }
};

// 客户端代码
int main() {
    // 创建文件
    auto file1 = std::make_shared<File>("readme.txt", 10);
    auto file2 = std::make_shared<File>("image.png", 2048);
    auto file3 = std::make_shared<File>("data.csv", 512);
    auto file4 = std::make_shared<File>("notes.txt", 5);

    // 创建文件夹
    auto docsFolder = std::make_shared<Folder>("文档");
    auto picsFolder = std::make_shared<Folder>("图片");
    auto rootFolder = std::make_shared<Folder>("根目录");

    // 构建文件系统结构
    docsFolder->add(file1);
    docsFolder->add(file4);
    picsFolder->add(file2);
    rootFolder->add(docsFolder);
    rootFolder->add(picsFolder);
    rootFolder->add(file3);

    // 显示整个文件系统(通过根节点统一操作)
    std::cout << "文件系统结构:" << std::endl;
    rootFolder->display();

    return 0;
}   
输出结果
复制代码
文件系统结构:
文件夹: 根目录 (包含 3 个项目)
--文件夹: 文档 (包含 2 个项目)
----文件: readme.txt (10KB)
----文件: notes.txt (5KB)
--文件夹: 图片 (包含 1 个项目)
----文件: image.png (2048KB)
--文件: data.csv (512KB)
应用场景
  1. 文件系统
    • 文件夹和文件组成的树形结构,支持统一的操作接口
  2. UI框架
    • 容器控件(如面板、窗口)包含其他控件(如按钮、文本框),形成树形结构
  3. 组织结构
    • 公司包含部门,部门包含小组,小组包含员工,形成层次结构
  4. 图形系统
    • 复杂图形由简单图形组合而成,如组合图形(CompositeShape)包含多个基本图形(Circle、Rectangle)
  5. 菜单系统
    • 菜单栏包含菜单,菜单包含菜单项或子菜单,形成树形结构

三、优化

优化点
  1. 泛型设计
    • 使用模板实现通用组件,支持任意数据类型(示例中用int表示文件大小)
    • 同一套组合模式可适用于不同业务场景(文件系统、UI组件、组织机构等)
  2. 类型安全与错误处理
    • 增加组件类型判断(isComposite()
    • 防止添加空组件、自身作为子组件等非法操作
    • 使用异常机制处理错误,提供更友好的错误信息
  3. 迭代器支持
    • 实现ComponentIterator接口,支持统一遍历组合组件
    • 客户端可通过迭代器访问子组件,无需了解内部存储结构
  4. 功能扩展接口
    • 增加getSize()方法,支持递归计算总大小
    • 组合组件添加countLeaves()方法,统计叶子节点数量
    • 保留扩展空间,可根据需求添加更多聚合操作
  5. 现代C++特性
    • 使用std::shared_ptr管理组件生命周期,避免内存泄漏
    • 利用STL算法(accumulateremove_if)简化代码
    • 使用override关键字明确重写关系,增强代码可读性
cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <iterator>
#include <numeric>
#include <typeinfo>
#include <stdexcept>

// 前向声明
template <typename T>
class Component;

// 迭代器接口 - 支持遍历组件
template <typename T>
class ComponentIterator {
public:
   using iterator_category = std::forward_iterator_tag;
   using value_type = std::shared_ptr<Component<T>>;
   using difference_type = std::ptrdiff_t;
   using pointer = value_type*;
   using reference = value_type&;

   virtual ~ComponentIterator() = default;
   virtual bool hasNext() const = 0;
   virtual value_type next() = 0;
   virtual void reset() = 0;
};

// 抽象组件基类 - 泛型设计支持不同类型组件
template <typename T>
class Component {
protected:
   std::string name_;
   T data_;  // 组件携带的数据

public:
   explicit Component(std::string name, T data = T{}) 
       : name_(std::move(name)), data_(std::move(data)) {}
   
   virtual ~Component() = default;

   // 基础接口
   std::string getName() const { return name_; }
   T getData() const { return data_; }
   void setData(T data) { data_ = std::move(data); }

   // 纯虚接口 - 必须实现
   virtual void display(int depth = 0) const = 0;
   virtual bool isComposite() const = 0;  // 区分叶子和组合

   // 组合操作 - 默认抛出异常,组合组件需重写
   virtual void add(std::shared_ptr<Component<T>>) {
       throw std::runtime_error("不支持添加操作: " + name_);
   }

   virtual void remove(const std::string& name) {
       throw std::runtime_error("不支持移除操作: " + name_);
   }

   virtual std::shared_ptr<Component<T>> getChild(const std::string& name) {
       throw std::runtime_error("不支持获取子组件: " + name_);
   }

   // 迭代器支持
   virtual std::unique_ptr<ComponentIterator<T>> createIterator() {
       throw std::runtime_error("不支持迭代器: " + name_);
   }

   // 功能扩展接口 - 计算组件大小(示例)
   virtual size_t getSize() const = 0;
};

// 叶子组件 - 不能包含子组件
template <typename T>
class Leaf : public Component<T> {
public:
   Leaf(std::string name, T data = T{}) 
       : Component<T>(std::move(name), std::move(data)) {}

   void display(int depth = 0) const override {
       std::string indent(depth, '-');
       std::cout << indent << "叶子: " << this->name_ 
                 << " (数据: " << this->data_ << ")" << std::endl;
   }

   bool isComposite() const override { return false; }

   // 叶子组件大小即为自身大小
   size_t getSize() const override {
       return sizeof(*this);  // 实际应用中可返回真实数据大小
   }
};

// 组合组件迭代器实现
template <typename T>
class CompositeIterator : public ComponentIterator<T> {
private:
   std::vector<std::shared_ptr<Component<T>>> children_;
   size_t currentIndex_ = 0;

public:
   explicit CompositeIterator(std::vector<std::shared_ptr<Component<T>>> children)
       : children_(std::move(children)) {}

   bool hasNext() const override {
       return currentIndex_ < children_.size();
   }

   typename ComponentIterator<T>::value_type next() override {
       if (!hasNext()) {
           throw std::out_of_range("迭代器已到达末尾");
       }
       return children_[currentIndex_++];
   }

   void reset() override {
       currentIndex_ = 0;
   }
};

// 组合组件 - 可以包含子组件
template <typename T>
class Composite : public Component<T> {
private:
   std::vector<std::shared_ptr<Component<T>>> children_;

   // 类型检查辅助函数
   template <typename U>
   bool isType(const std::shared_ptr<Component<T>>& component) const {
       return typeid(*component) == typeid(U);
   }

public:
   explicit Composite(std::string name, T data = T{}) 
       : Component<T>(std::move(name), std::move(data)) {}

   // 添加子组件(带类型检查)
   void add(std::shared_ptr<Component<T>> component) override {
       if (!component) {
           throw std::invalid_argument("不能添加空组件");
       }
       if (component.get() == this) {
           throw std::invalid_argument("不能添加自身作为子组件");
       }
       children_.push_back(std::move(component));
   }

   // 移除子组件
   void remove(const std::string& name) override {
       auto it = std::remove_if(children_.begin(), children_.end(),
           [&name](const std::shared_ptr<Component<T>>& comp) {
               return comp->getName() == name;
           });
       
       if (it != children_.end()) {
           children_.erase(it, children_.end());
       } else {
           throw std::out_of_range("未找到子组件: " + name);
       }
   }

   // 获取子组件
   std::shared_ptr<Component<T>> getChild(const std::string& name) override {
       for (const auto& child : children_) {
           if (child->getName() == name) {
               return child;
           }
       }
       return nullptr;
   }

   // 显示组件及子组件
   void display(int depth = 0) const override {
       std::string indent(depth, '-');
       std::cout << indent << "组合: " << this->name_ 
                 << " (包含 " << children_.size() << " 个子组件)" << std::endl;

       // 递归显示子组件
       for (const auto& child : children_) {
           child->display(depth + 2);
       }
   }

   bool isComposite() const override { return true; }

   // 创建迭代器
   std::unique_ptr<ComponentIterator<T>> createIterator() override {
       return std::make_unique<CompositeIterator<T>>(children_);
   }

   // 计算总大小(递归计算所有子组件)
   size_t getSize() const override {
       return std::accumulate(children_.begin(), children_.end(), 
           sizeof(*this),  // 自身大小
           [](size_t total, const std::shared_ptr<Component<T>>& child) {
               return total + child->getSize();
           }
       );
   }

   // 扩展功能:统计叶子节点数量
   size_t countLeaves() const {
       size_t count = 0;
       for (const auto& child : children_) {
           if (child->isComposite()) {
               // 向下转型调用组合组件的方法
               auto composite = std::dynamic_pointer_cast<Composite<T>>(child);
               if (composite) {
                   count += composite->countLeaves();
               }
           } else {
               count++;
           }
       }
       return count;
   }
};

// 客户端代码 - 文件系统示例
int main() {
   try {
       // 创建文件(叶子组件,数据为文件大小KB)
       auto file1 = std::make_shared<Leaf<int>>("readme.txt", 10);
       auto file2 = std::make_shared<Leaf<int>>("image.png", 2048);
       auto file3 = std::make_shared<Leaf<int>>("data.csv", 512);
       auto file4 = std::make_shared<Leaf<int>>("notes.txt", 5);

       // 创建文件夹(组合组件)
       auto docsFolder = std::make_shared<Composite<int>>("文档");
       auto picsFolder = std::make_shared<Composite<int>>("图片");
       auto rootFolder = std::make_shared<Composite<int>>("根目录");

       // 构建层次结构
       docsFolder->add(file1);
       docsFolder->add(file4);
       picsFolder->add(file2);
       rootFolder->add(docsFolder);
       rootFolder->add(picsFolder);
       rootFolder->add(file3);

       // 显示整个结构
       std::cout << "文件系统结构:" << std::endl;
       rootFolder->display();

       // 使用迭代器遍历根目录子组件
       std::cout << "\n根目录子组件列表:" << std::endl;
       auto iterator = rootFolder->createIterator();
       while (iterator->hasNext()) {
           auto comp = iterator->next();
           std::cout << "- " << comp->getName() 
                     << (comp->isComposite() ? " (文件夹)" : " (文件)") << std::endl;
       }

       // 功能扩展演示
       std::cout << "\n统计信息:" << std::endl;
       std::cout << "总大小: " << rootFolder->getSize() << " 字节" << std::endl;
       std::cout << "文件总数: " << rootFolder->countLeaves() << " 个" << std::endl;

       // 测试错误处理
       try {
           file1->add(file2);  // 叶子组件不能添加子组件
       } catch (const std::exception& e) {
           std::cout << "\n错误处理测试: " << e.what() << std::endl;
       }

   } catch (const std::exception& e) {
       std::cerr << "发生错误: " << e.what() << std::endl;
       return 1;
   }

   return 0;
}   
输出结果
复制代码
文件系统结构:
组合: 根目录 (包含 3 个子组件)
--组合: 文档 (包含 2 个子组件)
----叶子: readme.txt (数据: 10)
----叶子: notes.txt (数据: 5)
--组合: 图片 (包含 1 个子组件)
----叶子: image.png (数据: 2048)
--叶子: data.csv (数据: 512)

根目录子组件列表:
- 文档 (文件夹)
- 图片 (文件夹)
- data.csv (文件)

统计信息:
总大小: 40 字节
文件总数: 4 个

错误处理测试: 不支持添加操作: readme.txt
适用场景扩展

优化后的组合模式更适合:

  • 复杂树形结构的管理(如多级菜单、嵌套控件)
  • 需要统一遍历接口的场景(如递归计算、搜索)
  • 频繁扩展功能的系统(通过扩展接口添加新操作)
  • 对类型安全和内存管理有严格要求的生产环境
相关推荐
星星火柴9365 小时前
关于“双指针法“的总结
数据结构·c++·笔记·学习·算法
Zyy~5 小时前
《设计模式》装饰模式
java·设计模式
艾莉丝努力练剑6 小时前
【洛谷刷题】用C语言和C++做一些入门题,练习洛谷IDE模式:分支机构(一)
c语言·开发语言·数据结构·c++·学习·算法
落霞的思绪8 小时前
Java设计模式详细解读
java·开发语言·设计模式
阿巴~阿巴~8 小时前
深入解析C++ STL链表(List)模拟实现
开发语言·c++·链表·stl·list
是2的10次方啊8 小时前
🚀 JDK设计模式大揭秘:23种模式藏在你每天在用的类里
设计模式
步行cgn9 小时前
设计模式(Design Patterns)
设计模式
旺小仔.9 小时前
双指针和codetop复习
数据结构·c++·算法
jingfeng5149 小时前
C++ STL-string类底层实现
前端·c++·算法