当前位置: 首页 > news >正文

设计模式 17 组合模式 Composite Pattern

设计模式 17 组合模式 Composite Pattern

1.定义


组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。它允许你将对象组合成树形结构,以表示“部分-整体”层次关系。它将单个对象和组合对象都视为相同类型的对象,从而使你能够统一地处理它们

核心思想:

将单个对象和组合对象都视为相同类型的对象,即它们都实现相同的接口或抽象类。
组合对象可以包含其他对象,形成树形结构。
客户端代码可以统一地处理单个对象和组合对象。

2.内涵

组合模式的内涵在于它提供了一种 统一处理单个对象和组合对象 的方法,从而简化代码、提高可扩展性和可重用性。

核心内涵

  • 树形结构: 组合模式的核心是构建一个树形结构,以表示“部分-整体”层次关系。树的节点可以是单个对象(叶子节点)或组合对象(非叶子节点)。
  • 统一接口: 组合模式要求所有组件(包括单个对象和组合对象)都实现相同的接口或抽象类。这使得客户端代码可以统一地处理所有组件,而无需关心它们是单个对象还是组合对象。
  • 递归操作: 组合模式通常使用递归来处理组合对象。当对组合对象进行操作时,它会递归地对它的子组件进行相同操作。
  • 简化代码: 由于所有组件都具有相同的接口,客户端代码可以统一地处理它们,避免了对不同类型对象的特殊处理。
  • 提高可扩展性: 可以轻松地添加新的组件类型,而无需修改现有代码。因为新的组件只需要实现相同的接口即可。
  • 增强灵活性和可重用性: 可以灵活地组合不同的组件,以创建不同的结构,并可以将这些结构重用在不同的场景中


3.案例分析

#include <algorithm>
#include <iostream>
#include <list>
#include <string>class Component {/*** @var Component*/protected:Component *parent_;public:virtual ~Component() {}void SetParent(Component *parent) {this->parent_ = parent;}Component *GetParent() const {return this->parent_;}virtual void Add(Component *component) {}virtual void Remove(Component *component) {}virtual bool IsComposite() const {return false;}virtual std::string Operation() const = 0;
};class Leaf : public Component {public:std::string Operation() const override {return "Leaf";}
};class Composite : public Component {protected:std::list<Component *> children_;public:void Add(Component *component) override {this->children_.push_back(component);component->SetParent(this);}void Remove(Component *component) override {children_.remove(component);component->SetParent(nullptr);}bool IsComposite() const override {return true;}std::string Operation() const override {std::string result;for (const Component *c : children_) {if (c == children_.back()) {result += c->Operation();} else {result += c->Operation() + "+";}}return "Branch(" + result + ")";}
};void ClientCode(Component *component) {// ...std::cout << "RESULT: " << component->Operation();// ...
}void ClientCode2(Component *component1, Component *component2) {// ...if (component1->IsComposite()) {component1->Add(component2);}std::cout << "RESULT: " << component1->Operation();// ...
}int main() {Component *simple = new Leaf;std::cout << "Client: I've got a simple component:\n";ClientCode(simple);std::cout << "\n\n";Component *tree = new Composite;Component *branch1 = new Composite;Component *leaf_1 = new Leaf;Component *leaf_2 = new Leaf;Component *leaf_3 = new Leaf;branch1->Add(leaf_1);branch1->Add(leaf_2);Component *branch2 = new Composite;branch2->Add(leaf_3);tree->Add(branch1);tree->Add(branch2);std::cout << "Client: Now I've got a composite tree:\n";ClientCode(tree);std::cout << "\n\n";std::cout << "Client: I don't need to check the components classes even when managing the tree:\n";ClientCode2(tree, simple);std::cout << "\n";delete simple;delete tree;delete branch1;delete branch2;delete leaf_1;delete leaf_2;delete leaf_3;return 0;
}

以上代码UML图如下所示:


4.注意事项


在使用组合模式进行开发时,需要考虑以下几个注意事项:

1. 避免循环引用:

组合模式中,组件之间可以相互嵌套,形成树形结构。如果出现循环引用,会导致无限递归,最终导致程序崩溃。
例如,文件夹 A 包含文件夹 B,文件夹 B 又包含文件夹 A,就会形成循环引用。
避免循环引用的方法是仔细设计组件之间的关系,确保没有相互依赖的循环。
2. 谨慎使用递归:

组合模式中,通常使用递归来处理组合对象。递归虽然方便,但可能会导致栈溢出,尤其是在处理大型树形结构时。
为了避免栈溢出,可以考虑使用迭代的方式来代替递归,或者使用尾递归优化。
3. 考虑性能:

组合模式中,对组合对象的访问可能会涉及多个子组件的访问,因此需要考虑性能问题。
为了提高性能,可以考虑使用缓存机制,或者使用更轻量级的结构来代替树形结构。
4. 确保接口的完整性:

组合模式中,所有组件都必须实现相同的接口。因此,需要确保接口的完整性,包含所有必要的操作方法。
接口应该尽可能地抽象,避免与具体实现细节相关联。
5. 避免过度使用:

组合模式是一种强大的模式,但它并不适合所有场景。如果你的系统结构比较简单,或者没有明显的“部分-整体”层次关系,则不需要使用组合模式。
在选择设计模式时,需要权衡利弊,选择最适合的模式


5.最佳实践

组合模式是一个强大的工具,但要有效地运用它,需要遵循一些最佳实践:

1. 明确“部分-整体”层次关系:

首先,要确保你的系统中存在明显的“部分-整体”层次关系。例如,文件系统中的文件夹和文件,组织结构中的部门和员工,图形界面中的容器和组件等。
只有在存在这种层次关系的情况下,组合模式才能发挥其优势。
2. 设计清晰的组件接口:

定义一个抽象的 Component 接口,所有组件(包括单个对象和组合对象)都必须实现这个接口。
接口应该包含所有必要的操作方法,例如 add(), remove(), getChild(), getName(), getSize() 等,这些方法应该能够适用于所有类型的组件。
3. 确保接口的完整性:

接口应该尽可能地抽象,避免与具体实现细节相关联。
同时,接口应该包含所有必要的操作方法,以支持所有可能的用例。
4. 谨慎使用递归:

递归是处理组合对象的一种常见方式,但它可能会导致栈溢出,尤其是在处理大型树形结构时。
可以考虑使用迭代的方式来代替递归,或者使用尾递归优化。
5. 考虑性能:

在处理大型树形结构时,性能是一个重要因素。
可以考虑使用缓存机制,或者使用更轻量级的结构来代替树形结构。
6. 避免过度使用:

组合模式并不适合所有场景。如果你的系统结构比较简单,或者没有明显的“部分-整体”层次关系,则不需要使用组合模式。
在选择设计模式时,需要权衡利弊,选择最适合的模式。
7. 使用示例代码进行验证:

在实际应用中,可以使用示例代码来验证组合模式的实现是否符合预期。
通过测试用例,可以确保组合模式能够正确地处理各种情况。


6.总结


组合模式的内涵在于它通过统一接口和递归操作,将单个对象和组合对象统一起来,简化了代码,提高了可扩展性和可重用性。它为构建灵活、可扩展和可重用的树形结构提供了强大的支持。
 

相关文章:

  • 网页设计步骤总结
  • C++ Qt:QString与数字之间的相互转换
  • es和mongdb对比
  • Ai速递5.29
  • 0.25W 1.5KVDC~3KVDC 隔离超小型单输出 DC/DC 电源模块——TKE-W25系列
  • 重磅发布,2024精选《制造业商业智能BI最佳实践合集 》
  • 电量计量芯片HLW8110的前端电路设计与误差分析校正.pdf 下载
  • 一个程序员的牢狱生涯(44)询问
  • MOS管开关电路简单笔记
  • MySQL建库
  • stable Diffusion缺失模型补充地址
  • Android 版本与 API level 以及 NDK 版本对应
  • 原神抽卡点名程序教程(直接下载用)
  • 爬虫案例-亚马逊反爬分析-验证码突破(x-amz-captcha)
  • QT截图程序,可多屏幕截图二,增加调整截图区域功能
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 0x05 Python数据分析,Anaconda八斩刀
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • C++11: atomic 头文件
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • Effective Java 笔记(一)
  • Javascript编码规范
  • mysql innodb 索引使用指南
  • nodejs调试方法
  • python 学习笔记 - Queue Pipes,进程间通讯
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • Webpack 4x 之路 ( 四 )
  • Xmanager 远程桌面 CentOS 7
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 从0实现一个tiny react(三)生命周期
  • 记一次和乔布斯合作最难忘的经历
  • 前嗅ForeSpider采集配置界面介绍
  • 一天一个设计模式之JS实现——适配器模式
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #Linux(帮助手册)
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • $NOIp2018$劝退记
  • (bean配置类的注解开发)学习Spring的第十三天
  • (C#)获取字符编码的类
  • (C++17) optional的使用
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (顺序)容器的好伴侣 --- 容器适配器
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)详解PHP处理密码的几种方式
  • ***利用Ms05002溢出找“肉鸡
  • ... 是什么 ?... 有什么用处?
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .Net IOC框架入门之一 Unity
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换