Effective C++ 学习笔记 条款38 通过复合塑模出has-a或“根据某物实现出”

组合(composition) 是类型之间的一种关系------当一个类型的对象包含另一个类型的对象时,就产生了这种关系。例如:

在这个例子中,Person 对象由 string、Address 和 PhoneNumber 对象组合而成。在程序员中,"组合"这个词有很多同义词,也被称为分层(layering)、包含(containment)、聚合(aggregation)和嵌入(embedding)。

条款32指出,public 继承表示 is-a(是一种)。组合也有它的含义。实际上,组合有两种含义。组合既可以表示 has-a(有一个),也可以表示 is-implemented-in-terms-of(根据某物实现)。这是因为在你的软件中涉及两个不同的领域。程序中的某些对象对应于你正在建模的现实世界中的事物,比如人、车辆、视频帧等,这类对象属于应用域。另一些对象则纯粹是实现产物,比如缓冲区、互斥量、搜索树等,这类对象属于实现域。当组合发生在应用域的对象之间时,它表达的是 has-a 关系;当组合发生在实现域时,它表达的是 is-implemented-in-terms-of 关系。

上面的 Person 类展示的就是 has-a 关系。一个人对象有一个名字、一个地址、一个语音电话号码和一个传真电话号码。你不会说一个人是一个名字,也不会说一个人是一个地址------你会说一个人有一个名字、有一个地址。大多数人很容易区分这其中的差别,所以 is-a 和 has-a 之间的混淆相对较少。

稍微更麻烦一些的是 is-a 与 is-implemented-in-terms-of 之间的区别。比如,假设你需要一个模板来表示"小型对象集合"的类------也就是不带重复元素的集合。既然复用是一件美妙的事情,你的第一反应就是采用标准库中的 set 模板。既然已经有一个现成的模板可用,何必再写一个新的呢?

遗憾的是,set 的实现通常每个元素要承担三个指针的开销。这是因为 set 通常实现为平衡搜索树,以保证对数时间的查找、插入和删除。当速度比空间更重要时,这是一个合理的设计,但你的应用恰恰是空间比速度更重要。于是标准库的 set 对你的场景来说就做了错误的权衡。看来你还是得自己写模板。

不过,复用依然是一件美妙的事。作为精通数据结构的你,知道实现集合有很多选择,其中之一就是用链表。你也知道标准 C++ 库中有 list 模板,所以你决定(复)用它。

特别是,你决定让你那个新生的 Set 模板继承自 list。也就是说,Set<T> 将继承自 list<T>。毕竟在你的实现中,一个 Set 对象实际上就是一个 list 对象。于是你把 Set 模板声明成这样:

此时一切看似都还好,但实际上大有毛病。正如条款32所解释的,如果 D is-a B,那么所有对 B 为真的事情也对 D 为真。然而,一个 list 对象可以包含重复元素,所以如果把值 3051 两次插入一个 list<int>,该 list 将包含两份 3051。相比之下,Set 不允许包含重复元素,所以如果把值 3051 两次插入一个 Set<int>,该集合只会包含该值的一份副本。因此,"Set is-a list"并不成立,因为有些对 list 对象为真的事情,对 Set 对象并不为真。

因为这两个类之间的关系不是 is-a,所以用 public 继承来建模这种关系是错误的。正确的做法是认识到:一个 Set 对象可以根据一个 list 对象来实现:

Set 的成员函数可以大量依赖 list 和标准库其他部分已经提供的功能,因此只要你熟悉 STL 编程的基础知识,实现起来就很简单:

这些函数足够简单,很适合作为内联候选项,不过我明白你在做出明确的内联决定之前,会先回顾条款30中的相关讨论。

可以说,如果 Set 的接口遵循 STL 容器的惯例,它会更符合条款18中"设计易于正确使用、难以错误使用的接口"的建议。但若在此处遵循那些惯例,就不得不给 Set 添加大量内容,反而会模糊它与 list 之间的关系。既然这种关系正是本条款的讨论重点,我们就在教学清晰度上做一点取舍,暂时不追求 STL 兼容性。此外,关于 Set 接口的一些吹毛求疵,也不应掩盖 Set 中一个无可争辩的正确之处:即它与 list 之间的关系------那不是 is-a(尽管乍看之下可能像是),而是 is-implemented-in-terms-of(根据某物实现)。

切记:

1.组合的含义与 public 继承完全不同。

2.在应用域,组合表示 has-a(有一个);在实现域,组合表示 is-implemented-in-terms-of(根据某物实现)。

相关推荐
syagain_zsx19 小时前
库制作与原理 · 链接知识笔记
c语言·c++·笔记·动态库·静态库
qz5zwangzihan119 小时前
题解:Atcoder Beginner Contest abc467 A~D
c++·题解·atcoder·abc467
caimouse19 小时前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
智者知已应修善业20 小时前
【使用D触发器74HC175实现0001 0011 0111 1111 1110 1100 1000的循环彩灯电路。】2025-4-28
驱动开发·经验分享·笔记·硬件架构·硬件工程
叩码以求索1 天前
三维建模:SolidWorks 2025保姆级下载与安装教程指南
学习·数学建模
龙仔7251 天前
人大金仓KingbaseES V8 手动单库备份&恢复操作笔记
数据库·笔记·oracle·人大金仓
jinyishu_1 天前
模拟实现 C++ 栈和队列——从适配器模式看懂 STL 容器之美
java·c++·适配器模式
hehelm1 天前
AI大模型接入SDK—通用模块设计
linux·开发语言·c++
zhangrelay1 天前
消费电子原装锂电池服役8–20年且健康度>40%公开实测案例完整调研分析报告
笔记·学习