IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《设计模式》— 结构型模式 — 组合模式 -> 正文阅读

[C++知识库]《设计模式》— 结构型模式 — 组合模式

一、问题来源

在绘图编辑器和图形捕捉系统这样的图形应用程序中,我们可以通过组合已有的组件形成新的组件加入到工具箱中。一个简单的实现方式是为所有基本元素定义一些类,另外定义一些类作为这些图元的容器类。

这种方法存在一个问题:使用这些类的代码必须区别对待图元对象与容器对象,而实际上对于用户来说,它们是一致的。对这些类区别使用,将造成设计和实现上的困难。我们可以通过组合模式递归组合,使得用户不必对这些类进行区别:
在这里插入图片描述
Composite 模式的关键是一个抽象类,它既可以代表图元,又可以代表图元的容器。在图形系统中这个类就是 Graphic,它声明一些与特定图形对象相关的操作,例如 draw。同时它也声明了所有的组合对象共享的一些操作,例如一些操作用于访问和管理它的子部件。

子类 LineRectangleText 定义了一些图元对象,这些类实现 draw,分别用于绘制直线、矩形和文本。由于图元都没有子图形,因此它们都不执行与子类相关的操作。

Picture 类定义了一个 Graphic 对象的聚合。Picturedraw() 方法是通过对它的子部件调用 draw() 实习的。Picture 还用这种方法实现了一些与其子部件相关的操作。由于 Picture 接口与 Graphic 接口是一致的,因此其实例可以递归地组合其它 Picture 实例。

二、适用性

  • 你想表示对象的部分 - 整体层次结构
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

三、结构

组合模式的抽象类中既支持了图元的基本操作,也支持了图元容器的操作。我们这里不再展示操作,只画出递归结构:
在这里插入图片描述

四、参与者

1、Component(Graphic)

  • 为组合中的对象声明接口。
  • 在适当的情况下,实现所有类共有接口的缺省行为。
  • 声明一个接口用于访问和管理 Component 的子组件。
  • (可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。

2、Leaf

  • 在组合中表示叶节点对象,叶节点没有子节点。
  • 在组合中定义图元对象的行为。

3、Composite

  • 定义有子部件的那些部件的行为。
  • 存储子部件。
  • Component 接口中实现与子部件有关的操作。

4、Client

通过 Component 接口操纵组合部件的对象。

五、协作

用户使用 Component 类接口与组合结构中的对象进行交互。如果接受者是一个叶节点,则直接处理请求。如果接受者是 Composite,它通常将请求发送给它的子部件,在转发请求之前和 / 或之后可能执行一些辅助操作。

六、效果

  • 定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象有可以被组合,这样不断递归下去。俗称无限套娃。
  • 简化客户代码。客户不必关心叶节点和组合对象的区别。
  • 使得更容易增加新类型的组件
  • 使得设计更加容易一般化。容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用 Composite 时,你不能依赖类型系统施加这些约束,而必须在运行时进行检查。

七、实现

1、显式的父部件引用

保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件信用可以简化结构的上移和组件的删除,同时父部件引用也支持 责任链模式

通常在 Component 中定义父部件引用。LeafComposite 类可以继承这个引用以及管理这个引用的那些操作。

对于父部件引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,反之,该组合以这些节点为子节点。保证这一点最容易的办法是,仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件。如果能在 Composite 类的 AddRemove 方法中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式。

2、共享组件

共享组件是很有用的,比如它可以减少对存储的需求。但是当一个组件只有一个父部件时,很难共享组件。

一个可行的解决办法是为子部件存储多个父部件,但当一个请求在结构上向上传递时,这种方法会导致多义性。我们可以借助享元模式解决这种冲突。如果子部件可以将一些状态存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。

3、最大化Component接口

组合模式的目的之一是使得用户不知道他们正在使用的具体的 LeafComposite 类。为了达到这一目的,Componenet 类应尽量多定义一些公共操作。Componenet 类通常为这些操作提供缺省的实现,子类中可以重定义这些操作。

然而,这个目标可能会和可能会与类层次接口设计原则相冲突,该原则规定:一个类只能定义那些对它的子类有意义的操作。有许多 Component 所支持的操作对 Leaf 类似乎没有什么意义,那么 Component 该怎样为它们提供一个缺省的实现呢?我们可以把 Leaf 当做容器中元素个数为0的 Composite

4、声明管理子部件的操作

虽然 Composite 类实现了 AddRemove 操作用于管理子部件,但在组合模式中一个重要的问题是:在 Composite 类层次接口中哪些类声明这些操作。我们是应该在 Component 中声明这些操作,并使这些操作对 Leaf 类有意义,还是只应该在 Composite 和它的子类中声明并定义这些操作呢?

这需要在透明性安全性之间做出选择:

  • 在类层次结构的根部定义子节点管理接口的方法具有良好的透明性,因为你可以一致地使用所有的组件,但是这一方法是以安全性为代价的,因为客户有可能会做一些无意义的事情,例如在 Leaf 中增加和删除对象。
  • Composite 类中定义管理子部件的方法具有良好的安全性,因为像C++这样的静态类型语言中,在编译时任何从 Leaf 中增加或删除对象的尝试都将被发现。但是这又损失了透明性,因为 LeafComposite 具有不同的接口。

在组合模式中,相对于安全性,我们比较强调透明性。如果你选择了安全性,有可能会丢失类型信息,并且不得不将一个组件转换成一个组合。这样的类型转换必定不是类型安全的。因此,如果想实现安全有意义的透明线,我们不可避免的要在子类中的 addremove 方法中增加抛异常等失败处理。

5、Component 是否应该实现一个 Component 列表

你可能希望在 Component 类中将子节点集合定义为一个实例变量,而这个 Component 类中也声明了一些操作对子节点进行访问和管理。但是在基类中存放子类指针,对叶节点来说会导致空间浪费,因为叶节点根本没有子节点。只有当该结构中子类数目相对较少时,才值得使用这种方法。

6、子部件排序

许多设计指定了 Composite 的子部件顺序。在前面的 Graphic 例子中,排序可能表示了从前至后的顺序。如果 Composite 表示语法分析树,Composite 子部件的顺序必须反映程序结构,而组合语句就是这样一些 Composite 的实例、

如果需要考虑子节点的顺序,必须仔细地设计对子节点的访问和管理接口,以便管理子节点序列。

7、使用高速缓冲存储改善性能

如果需要对组合进行频繁的遍历或查找,Composite 类可以缓冲存储对它的子节点进行遍历或查找的相关信息。Composite 可以缓冲存储实际结果或者仅仅是一些用于缩短遍历或查询长度的信息。例如,我们在绘图系统中,Picture 类可以缓冲存储其子部件的边界框,在绘图或选择期间,当子部件在当前窗口不可见时,这个边界框使得 Picture 不需要再进行绘图或选择。

一个组件发生变化时,它的父部件原先缓冲存储的信息也变得无效。在组件知道其父部件时,这种方法最为有效。因此,如果你使用高速缓冲存储,需要定义一个接口来通知组合组件它们所缓冲存储的信息无效。

8、应该由谁删除 Component

在没有垃圾回收机制的语言中,当一个 Composite 被销毁时,通常最好由 Composite 负责删除其子节点。

八、代码示例

有很多现有的框架和语言中都使用了组合模式,例如 js 中的 DOM 对象管理,Qt 中的 QObject 管理。其中 QObject 管理实现了父组件的维护,子部件的管理,隐式的 Component 列表维护,父节点析构时对子节点的删除功能。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-14 15:45:21  更:2021-12-14 15:46:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:55:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码