组合模式及优化

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

一、介绍

核心角色

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

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

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

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

二、实现

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

#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;// 递归显示子组件,深度+1for (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关键字明确重写关系,增强代码可读性
#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
适用场景扩展

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

  • 复杂树形结构的管理(如多级菜单、嵌套控件)
  • 需要统一遍历接口的场景(如递归计算、搜索)
  • 频繁扩展功能的系统(通过扩展接口添加新操作)
  • 对类型安全和内存管理有严格要求的生产环境

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93551.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【wmi异常】关于taskkill命令提示“错误:找不到” 以及无法正常获取设备机器码的处理办法

记录一下我的解决方案。 我先查阅了这篇博客&#xff1a;https://blog.csdn.net/qq_45698181/article/details/138957277 发现他写的批处理不知怎么执行不了&#xff0c;后来问了ai又可以执行了&#xff0c;估计是csdn防盗版格式问题 这里写一下我跟ai的对话&#xff0c;大家可…

制造装配、仓储搬运、快递装卸皆适配!MinkTec 弯曲形变传感器助力,让人体工学改变劳动生活

【导语】Minktec 最新实验显示&#xff1a;将Minktec 柔性弯曲形变传感器FlexTail 贴于受试者背部&#xff0c;记录 1 分钟内从洗碗机取餐具的动作&#xff0c;结合配套的flexlib -专用Python库分析&#xff0c;不仅量化出 “越低越伤腰” 的结论&#xff0c;更为制造装配、物流…

Nginx蜘蛛请求智能分流:精准识别爬虫并转发SEO渲染服务

> 一招解决搜索引擎爬虫无法解析现代前端框架的痛点,提升网站收录率与SEO排名! **痛点场景**:你的网站采用Vue/React等前端框架构建,页面内容依赖JavaScript动态渲染。搜索引擎爬虫访问时,只能抓取到空HTML骨架,无法获取真实内容,导致网站收录率低、SEO效果差。 --…

链表。。。

目录 5.1 链表的结点 5.2 插入 5.3 链表长度 5.4 查找 5.5 指定位置删除 5.6 代码 5.1 链表的结点 一个结点包括&#xff1a;值和指向下一个结点的指针。 package com.qcby.链表;public class Node {int value;Node next;public Node(int val){valueval;}Overridepublic…

私人AI搜索新突破:3步本地部署Dify+Ollama+QwQ,搜索能力MAX

1.安装Docker容器 本地部署Dify要先安装Docker桌面版&#xff0c;跟Ollama一样简单&#xff0c;也是去官网下载对应版本文件&#xff0c;直接安装就OK。 2&#xff1a;安装Dify 安装 Dify 简单的方式就是git clone&#xff0c;复制其github地址github.com/langgenius/dify&am…

(2-10-1)MyBatis的基础与基本使用

目录 0.前置小节 1. MyBatis 框架介绍 1.1 软件开发中的框架 1.2 使用框架的好处 1.3 SSM 开发框架 1.4 什么是 MyBatis 1.5 MyBatis 的开发流程 2. MyBatis 的开发流程 2.0 MyBatis的工作流程 2.1 引入 MyBatis 依赖 00.base(目录、pom、单元测试、Junit4) 01.Cal…

StarRocks集群部署

Starrocks 是一款基于 MPP 架构的高性能实时分析型数据库&#xff0c;专为 OLAP&#xff08;联机分析处理&#xff09;场景 设计&#xff0c;尤其擅长处理海量数据的实时分析、复杂查询和多维统计。 硬件 CPU&#xff1a;StarRocks依靠AVX2指令集充分发挥其矢量化能力。因此&am…

【CPP】自己实现一个CPP小工具demo,可以扩展其他选项

自己写CPP脚本小工具1. 思路描述2. 代码实现2.1 代码文件CppTool.cpp2.2 CMakeLists.txt3. 工具示例3.1 帮助信息3.2 工具用法3.3 实际使用1. 思路描述 实现一个简单的命令行工具。内容包括&#xff1a; 命令帮助信息参数检查&#xff0c;参数解析等功能。执行其他命令。将指…

如何使用嵌入模型创建本地知识库Demo

为data目录下的txt文档用阿里百炼的文本嵌入模型创建一个本地知识库import os from llama_index.core import ,Settings, SimpleDirectoryReader, VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.llms.dashscope import DashSc…

SpringBoot 整合 Langchain4j:系统提示词与用户提示词实战详解

> 掌握提示词工程的核心技巧,让你的AI应用效果提升300%! **真实痛点**:为什么同样的模型,别人的应用精准专业,而你的却答非所问?关键在于提示词工程!本文将揭秘如何通过系统提示词与用户提示词的巧妙配合,打造专业级AI应用。 --- ### 一、Langchain4j 核心概念…

Sklearn 机器学习 邮件文本分类 加载邮件数据

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Sklearn 机器学习 邮件文本分类 - 加载邮件数据 在自然语言处理(NLP)中,邮件文本分…

腾讯云开发小程序工具箱使用心得

一、核心优势与使用体验 作为首批使用腾讯云开发&#xff08;CloudBase&#xff09;工具箱的开发者&#xff0c;我深刻感受到其通过CloudBase AI与MCP服务重构开发范式的创新价值。结合微信小程序开发场景&#xff0c;该平台在以下维度表现突出&#xff1a; 1. AI驱动的全栈开发…

机械加工元件——工业精密制造的璀璨明珠

在工业制造的宏大画卷中&#xff0c;机械加工元件犹如璀璨的明珠&#xff0c;以其卓越的性能和精湛的工艺&#xff0c;为各行各业的发展注入了源源不断的动力。它们虽形态各异&#xff0c;功能不同&#xff0c;却在无数产品中携手合作&#xff0c;展现出科技与柔性的完美融合。…

【八股】Redis-中小厂精要八股

Redis 基础 redis为什么这么快 (高) [!NOTE] 最首要的是Redis是纯内存操作, 比磁盘要快3个数量级同时在与内存操作中采用了非阻塞I/O多路复用机制来提高并发量并且基于Redis的IO密集型&#xff0c;采用单线程操作, 免去了线程切换开销Redis 内置了多种优化过后的数据结构实现…

C++字符串(string)操作解析:从基础到进阶

1. 字符串基础&#xff1a;大小与容量cppvoid test1() {string s1("Hello World");cout << "size : " << s1.size() << endl; // 输出字符串长度cout << "capacity " << s1.capacity() << endl; // 输出字…

蘑兔音乐:音乐创作的魔法棒

在这个充满创意与可能的时代&#xff0c;人人都有一颗渴望表达音乐之心。但传统音乐创作&#xff0c;复杂的乐理、昂贵的设备&#xff0c;总让人望而却步。别担心&#xff01;蘑兔 AI 音乐强势来袭&#xff0c;它就是那个能让音乐小白也能搞创作的神奇工具&#xff01;​灵感模…

从传统到智能:RFID 技术如何重构压缩机生产线

从传统到智能&#xff1a;RFID 技术如何重构压缩机生产线在工业 4.0 与中国制造 2025 战略的深入推进下&#xff0c;作为空调核心部件的压缩机制造业正加速从传统生产模式向智能化转型。压缩机生产以高精度、大批量为显著特点&#xff0c;长期面临生产数据断层、柔性化不足、质…

HTML5二十四节气网站源码

一. 二十四节气文化主题网站概述 本网站以中国传统文化瑰宝“二十四节气”为核心&#xff0c;通过现代Web技术打造沉浸式文化体验平台&#xff0c;融合视觉美学与交互创新&#xff0c;全方位展现节气的自然规律与人文内涵。网站采用响应式布局设计&#xff0c;适配多终端设备&…

微服务架构实战指南:从单体应用到云原生的蜕变之路

&#x1f31f; Hello&#xff0c;我是蒋星熠Jaxonic&#xff01; &#x1f308; 在浩瀚无垠的技术宇宙中&#xff0c;我是一名执着的星际旅人&#xff0c;用代码绘制探索的轨迹。 &#x1f680; 每一个算法都是我点燃的推进器&#xff0c;每一行代码都是我航行的星图。 &#x…

超越Transformer:大模型架构创新的深度探索

引言&#xff1a; 以GPT、Claude、Gemini等为代表的大语言模型&#xff08;LLMs&#xff09;已成为人工智能领域的核心驱动力。它们基于Transformer架构构建&#xff0c;在理解和生成人类语言方面展现出惊人的能力。然而&#xff0c;随着模型规模指数级增长和对更长上下文、更高…