【C++设计模式】第八篇:组合模式(Composite)

注意:复现代码时,确保 VS2022 使用 C++17/20 标准以支持现代特性。

树形结构的统一操作接口


1. 模式定义与用途

核心思想

  • 组合模式:将对象组合成树形结构以表示"部分-整体"层次结构,使得客户端可以统一处理单个对象和组合对象。
  • 关键用途
    1.简化递归结构操作(如遍历目录树、渲染UI控件树)。
    2.隐藏复杂结构的差异,提供一致性接口。

​经典场景

  1. 文件系统管理(文件与文件夹的统一操作)。
  2. 图形界面容器(窗口包含按钮、面板等子控件)。

2. 模式结构解析

UML类图

plaintext 复制代码
+---------------------+  
|      Component      |  
+---------------------+  
| + add(c: Component) |  
| + remove(c: Component)|  
| + operation()       |  
+---------------------+  
          ^  
          |  
  +-------+-------+  
  |               |  
+-----------------+ +-----------------+  
|    Composite    | |      Leaf       |  
+-----------------+ +-----------------+  
| - children: list | | + operation()  |  
| + operation()    | +-----------------+  
+-----------------+  

角色说明

  1. Component:抽象接口,定义叶子和容器的共同操作(如add、remove)。
  2. Leaf:叶子节点,无子组件(如文件)。
  3. Composite:容器节点,存储子组件并实现递归逻辑(如文件夹)。

3. 简单示例:透明模式(统一接口)​

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

// 抽象组件(透明模式:叶子与容器接口一致)  
class FileSystemComponent {  
public:  
    virtual ~FileSystemComponent() = default;  
    virtual void add(std::shared_ptr<FileSystemComponent> item) {  
        throw std::runtime_error("叶子节点不支持此操作");  
    }  
    virtual void list() const = 0;  
};  

// 叶子:文件  
class File : public FileSystemComponent {  
public:  
    File(const std::string& name) : name_(name) {}  

    void list() const override {  
        std::cout << "文件: " << name_ << "\n";  
    }  

private:  
    std::string name_;  
};  

// 容器:文件夹  
class Folder : public FileSystemComponent {  
public:  
    Folder(const std::string& name) : name_(name) {}  

    void add(std::shared_ptr<FileSystemComponent> item) override {  
        children_.push_back(item);  
    }  

    void list() const override {  
        std::cout << "文件夹: " << name_ << "\n";  
        for (const auto& child : children_) {  
            child->list();  
        }  
    }  

private:  
    std::string name_;  
    std::vector<std::shared_ptr<FileSystemComponent>> children_;  
};  

// 使用示例  
int main() {  
    auto root = std::make_shared<Folder>("根目录");  
    auto docs = std::make_shared<Folder>("文档");  
    auto file1 = std::make_shared<File>("简历.pdf");  
    auto file2 = std::make_shared<File>("笔记.txt");  

    docs->add(file1);  
    docs->add(file2);  
    root->add(docs);  

    root->list();  // 递归列出所有内容  
}  

4. 完整代码:安全模式与扩展功能

场景:图形界面控件树

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

// 抽象组件(安全模式:叶子与容器接口分离)  
class UIComponent {  
public:  
    virtual ~UIComponent() = default;  
    virtual void render() const = 0;  
    virtual std::string getName() const = 0;  
};  

// 容器接口(仅容器支持添加/删除)  
class UIContainer : public UIComponent {  
public:  
    virtual void add(std::shared_ptr<UIComponent> child) = 0;  
    virtual void remove(std::shared_ptr<UIComponent> child) = 0;  
};  

// 叶子:按钮  
class Button : public UIComponent {  
public:  
    Button(const std::string& name) : name_(name) {}  

    void render() const override {  
        std::cout << "渲染按钮: " << name_ << "\n";  
    }  

    std::string getName() const override {  
        return name_;  
    }  

private:  
    std::string name_;  
};  

// 容器:面板  
class Panel : public UIContainer {  
public:  
    Panel(const std::string& name) : name_(name) {}  

    void add(std::shared_ptr<UIComponent> child) override {  
        children_.push_back(child);  
    }  

    void remove(std::shared_ptr<UIComponent> child) override {  
        auto it = std::find(children_.begin(), children_.end(), child);  
        if (it != children_.end()) {  
            children_.erase(it);  
        }  
    }  

    void render() const override {  
        std::cout << "渲染面板: " << name_ << "\n";  
        for (const auto& child : children_) {  
            child->render();  
        }  
    }  

    std::string getName() const override {  
        return name_;  
    }  

private:  
    std::string name_;  
    std::vector<std::shared_ptr<UIComponent>> children_;  
};  

// 客户端代码  
int main() {  
    auto mainPanel = std::make_shared<Panel>("主面板");  
    auto button1 = std::make_shared<Button>("确定");  
    auto button2 = std::make_shared<Button>("取消");  
    auto subPanel = std::make_shared<Panel>("子面板");  
    auto button3 = std::make_shared<Button>("更多");  

    subPanel->add(button3);  
    mainPanel->add(button1);  
    mainPanel->add(button2);  
    mainPanel->add(subPanel);  

    mainPanel->render();  
    // 输出:  
    // 渲染面板: 主面板  
    // 渲染按钮: 确定  
    // 渲染按钮: 取消  
    // 渲染面板: 子面板  
    // 渲染按钮: 更多  
}  

5. 优缺点分析

优点 ​​缺点
统一处理简单与复杂对象 透明模式违反接口隔离原则(叶子支持add)
支持递归操作与树形遍历 容器需管理子节点生命周期,增加复杂度
灵活扩展新组件类型 对性能敏感场景不友好(深层次遍历)

6. 调试与优化策略

调试技巧(VS2022)​

  1. 递归调用跟踪
    在render()方法内设置条件断点(如getName() == "子面板")。
  2. 内存泄漏检测
    使用VS2022内置诊断工具(Debug > Windows > Memory Usage)。

性能优化

  1. 缓存遍历结果
cpp 复制代码
class CachedPanel : public Panel {  
public:  
    void render() const override {  
        if (!cached_) {  
            cachedOutput_ = generateRenderOutput();  
            cached_ = true;  
        }  
        std::cout << cachedOutput_;  
    }  
private:  
    mutable bool cached_ = false;  
    mutable std::string cachedOutput_;  
};  
  1. 并行化渲染
cpp 复制代码
#include <execution>  
void Panel::render() const {  
    std::for_each(std::execution::par, children_.begin(), children_.end(),  
        [](const auto& child) { child->render(); });  
}  
相关推荐
Dream it possible!5 小时前
LeetCode 热题 100_字符串解码(71_394_中等_C++)(栈)
c++·算法·leetcode
My Li.6 小时前
c++的介绍
开发语言·c++
邪恶的贝利亚8 小时前
C++之序列容器(vector,list,dueqe)
开发语言·c++
原来是猿8 小时前
蓝桥备赛(13)- 链表和 list(上)
开发语言·数据结构·c++·算法·链表·list
成功助力英语中国话8 小时前
SDK编程,MFC编程,WTL编程之间的关系
c++·mfc
仟濹8 小时前
【算法 C/C++】二维差分
c语言·c++·算法
总斯霖9 小时前
题解:士兵排列
数据结构·c++·算法
稳兽龙9 小时前
P4268 [USACO18FEB] Directory Traversal G
c++·算法·换根dp
放氮气的蜗牛9 小时前
C++从入门到精通系列教程之第十篇:异常处理与调试技巧
开发语言·jvm·c++
LL59621456910 小时前
CEF在MFC上的示例工程
c++·mfc·cef