c++如何实现组合模式 c++设计模式之Composite【实例】


组合模式通过Component抽象接口统一处理Leaf和Composite,使客户端无需区分节点类型即可一致操作树形结构;核心是用std::unique_ptr管理内存、基类提供默认异常抛出,并避免循环引用。

组合模式(Composite Pattern)在 C++ 中用于统一处理单个对象(Leaf)和复合对象(Composite),让客户端可以一致地操作整个树形结构。核心是定义一个公共接口,让叶子节点和容器节点都实现它,从而屏蔽结构差异。

一、明确角色与接口设计

组合模式有三个关键角色:

  • Component(抽象构件):声明所有子类共有的操作(如 addremoveoperation),提供默认空实现或纯虚函数;
  • Leaf(叶子):不包含子节点,只实现自身行为,对管理子节点的操作(如 add)可抛异常或忽略;
  • Composite(容器):持有 std::vector<:unique_ptr>> 等子节点集合,转发操作到子节点,并支持动态增删。

二、C++ 实现示例(文件系统模拟)

模拟“文件(File)”和“目录(Directory)”,二者都可调用 display(),目录还能添加子项:

// Component.h
class Component {
public:
    virtual ~Component() = default;
    virtual void display(int indent = 0) const = 0;
    virtual void add(std::unique_ptr) { 
        throw std::runtime_error("add not supported for this component"); 
    }
    virtual void remove(const std::string&) { 
        throw std::runtime_error("remove not supported for this component"); 
    }
};
// Leaf: File.h
class File : public Component {
    std::string name_;
public:
    explicit File(std::string n) : name_(std::move(n)) {}
    void display(int indent) const override {
        std::cout << std::string(indent, ' ') << "? " << name_ << '\n';
    }
};
// Composite: Directory.h
class Directory : public Component {
    std::string name_;
    std::vector> children_;
public:
    explicit Directory(std::string n) : name_(std::move(n)) {}
    
    void display(int indent) const override {
        std::cout << std::string(indent, ' ') << "? " << name_ << '\n';
        for (const auto& child : children_) {
            child->display(indent + 2);
        }
    }

    void add(std::unique_ptr child) override {
        children_.push_back(std::move(child));
    }

    void remove(const std::string& targetName) override {
        children_.erase(
            std::remove_if(children_.begin(), children_.end(),
                [&targetName](const auto& c) -> bool {
                    // 简单匹配(实际可用 dynamic_cast 或 typeid 判断)
                    return c && c->getName() == targetName;
                }),
            children_.end()
        );
    }

    // 辅助:为演示加一个 getName(实际中可用 visitor 或其他方式解耦)
    virtual std::string getName() const { return name_; }
};

三、使用组合模式构建树并遍历

客户端代码无需区分叶子或容器,统一调用 display()

int main() {
    auto root = std::make_unique("root");
    
    root->add(std::make_unique("readme.md"));
    
    auto src = std::make_unique("src");
    src->add(std::make_unique("main.cpp"));
    src->add(std::make_unique("utils.h"));
    
    auto test = std::make_unique("test");
    test->add(std::make_unique("test_main.cpp"));
    
    root->add(std::move(src));
    root->add(std::move(test));

    root->display(); // 递归输出整棵树
}

输出效果:

? root
? readme.md
? src
? main.cpp
? utils.h
? test
? test_main.cpp

四、关键细节与注意事项

  • 内存安全优先:用 std::unique_ptr 管理子节点,避免裸指针和资源泄漏;
  • 接口职责清晰:叶子类不实现无意义的 add/remove,但保留接口——由基类提供默认异常抛出,比返回错误码更符合 C++ 惯例;
  • 避免循环引用:Composite 不应强持有父节点指针(除非需要向上遍历),否则易导致析构问题;
  • 扩展性考虑:若需不同类型的访问逻辑(如统计大小、搜索、序列化),可配合 Visitor 模式,而非在 Component 中堆砌方法。

不复杂但容易忽略:组合模式的价值不在“能写出来”,而在于它把“是否为容器”的判断从客户端逻辑中彻底剥离——只要面向 Component 编程,树有多深、混合多杂,都不影响调用方式。


# ai  # c++  # 子类  # Directory  # 循环  # 指针  # 虚函数  # 纯虚函数  # 接口  #   # 对象  # display  # 客户端  # 遍历  # 递归  # 抛出  # 都不  # 还能  # 不应  # 而非  # 文件系统 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: c++中explicit(bool)的用法 c++条件性explicit【C++20】  php文件怎么变mp4保存_php输出视频流保存为mp4操作【操作】  c++中如何使用auto关键字_c++11类型推导用法说明  Win11怎么关闭应用权限_Windows11相机麦克风隐私管理  Win10怎么卸载鲁大师_Win10彻底卸载鲁大师方法【步骤】  Python lxml的etree和ElementTree有什么区别  Win11怎么关闭防火墙通知_屏蔽Win11安全中心安全警告弹窗【技巧】  Windows10如何更改盘符名称_Win10重命名硬盘分区卷标  Mac如何创建和管理多个桌面空间_Mac高效多任务处理【技巧】  短链接怎么自定义还原php_修改解码规则适配需求【汇总】  Win10怎样安装Excel数据分析工具_Win10安装分析工具包步骤【教程】  php中self::能调用子类重写的方法吗_静态绑定与重写关系【介绍】  静态属性修改会影响所有实例吗_php作用域操作符下静态存储【教程】  如何使用Golang log设置日志输出格式_Golang log日志格式示例  C++如何使用std::transform批量处理容器元素?(代码示例)  Win10怎样设置多显示器_Win10多显示器扩展设置【攻略】  win11如何清理传递优化文件 Win11为C盘瘦身删除更新缓存【技巧】  Win11怎么查看已连接wifi密码 Win11查已连wifi密码步骤【教程】  Go 语言标准库为何不提供泛型切片的 Contains 方法?  Mac如何设置动态壁纸?(让桌面动起来)  Python与MongoDB NoSQL开发实战_文档模型与索引优化  用Python构建微服务架构实践_FastAPI与Django对比详解  php中::能用于接口静态方法吗_接口静态方法调用规则【操作】  如何使用正则表达式批量替换重复的“-”模式为固定字符串  Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)  TestNG的testng.xml配置文件怎么写  Windows10系统怎么查看显卡型号_Win10 dxdiag显示选项卡  如何将文本文件中的竖排字符串转换为横排字符串  c++ std::atomic如何保证原子性 c++ CAS操作原理【底层】  php怎么下载安装后设置错误日志_phpini log配置教程【汇总】  如何在 IIS 上为 ASP.NET 6 应用排除特定目录并交由 PHP 处理  如何在JavaScript中动态拼接PHP的base_url与jQuery变量  如何在Golang中实现RPC异步返回_Golang RPC异步处理与回调方法  Win11怎么关闭触摸键盘图标_Windows11任务栏系统托盘设置  Win11如何设置开机自动联网 Win11宽带连接自动拨号【步骤】  Win11怎么设置快速访问_Windows11文件资源管理器主页  Win11怎么设置默认PDF阅读器 Win11修改PDF打开方式【步骤】  php中$this和::能混用吗_对象与静态作用域冲突解决【方法】  Win10如何更改电脑休眠时间_Windows10电源和睡眠选项调整  Drupal 中 HTML 链接被双重转义导致渲染异常的解决方案  C++中引用和指针有什么区别?(代码说明)  Windows 10怎么隐藏特定更新补丁_Windows 10使用微软官方工具wushowhide.diagcab  如何使用Golang reflect检查方法数量_动态分析类型方法  Windows10电脑怎么设置虚拟内存_Win10高级系统设置性能  Win11怎么更改电脑密码_Windows 11修改本地账户密码【步骤】  Win11键盘快捷键大全_Windows 11常用高效快捷键汇总【技巧】  如何高效删除 NumPy 二维数组中所有元素相同的列  如何处理“XML格式不正确”错误 常见XML well-formed问题解决方法  Python日志系统设计与实现_高可观测性架构实战  Win11怎么更改盘符_Win11磁盘管理修改驱动器号【步骤】 

 2025-12-25

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.