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

Item38--通过复合 (Composition) 塑模出 has-a

我们之前的 Item(如 32, 34, 36)都在讨论 Public Inheritance(公有继承),它的核心意义是 "Is-a"(是一个) 的关系。 而 Composition(复合/组合) ——即一个类包含另一个类型的对象作为成员变量——则代表了完全不同的两种意义:

  1. Has-a(拥有):应用域(Application Domain)的概念。
  2. Is-implemented-in-terms-of(根据某物实现出):实现域(Implementation Domain)的概念。

以下是深度解析。


1. 什么是复合 (Composition)?

非常简单,就是一个类里面有另一个类的对象。

class Address { ... };
class PhoneNumber { ... };class Person {
public:...
private:std::string name;        // CompositionAddress address;         // CompositionPhoneNumber voiceNumber; // CompositionPhoneNumber faxNumber;   // Composition
};

在这个例子中,Person 是由 string, Address, PhoneNumber 等对象组成的。这就是复合。


2. 第一层含义:Has-a(拥有)

这是最直观的含义,通常出现在应用域(即你正在建模的现实世界业务逻辑)中。

  • 逻辑: 人(Person)拥有一个名字(Name)。人拥有一个地址(Address)。
  • 反例: 你绝不会说“人是一个地址”(Person is an Address)。因此,Person 不应该继承 Address,而应该包含它。

这一点大多数人都不会搞错。


3. 第二层含义:Is-implemented-in-terms-of(根据某物实现出)

这是实现域(即纯粹为了写代码、数据结构、算法)中的概念。这也是最容易误用继承的地方。

场景案例:我们需要一个 Set(集合)

假设你需要实现一个 Set 模板类。你知道 Set 的特性是:

  • 元素不能重复。
  • 通常无序(或者是特定的数学顺序)。
  • 需要空间存储元素。

你手里正好有一个现成的、功能强大的 std::list。你想:“太好了,我可以复用 std::list 的代码来存储数据。”

❌ 错误的各种做法:使用 Public Inheritance

// 错误设计:让 Set 继承 List
template<typename T>
class Set : public std::list<T> { ... };

这就犯了 Item 32 ("Public inheritance means is-a") 的大忌。 如果 Set 继承了 List,那么 Set 就是一个 List。这就意味着适用于 List 的所有操作必须适用于 Set

  • List 可以包含重复元素。Set 不行。
  • List 可以在指定位置插入 (insert)。Set 通常不需要关心物理位置。
  • List 可以拼接 (splice)。

如果用户写了这行代码:

Set<int> s;
s.push_back(10); // 第一次
s.push_back(10); // 第二次!List 允许这样做,但 Set 不应该允许!

此时你的 Set 就破功了。因为它继承了 List 的接口,导致它的行为不再像一个 Set

✅ 正确的做法:使用 Composition

我们应该说:Set 是根据 List 实现出来的(Set is implemented in terms of List)。 Set 使用 List 来管理内存和数据,但 Set 不是 一个 List

template<typename T>
class Set {
public:// 只有 Set 该有的接口bool member(const T& item) const;void insert(const T& item);void remove(const T& item);std::size_t size() const;private:// 内部实现细节:使用 list 来干活std::list<T> rep; 
};template<typename T>
bool Set<T>::member(const T& item) const {return std::find(rep.begin(), rep.end(), item) != rep.end();
}template<typename T>
void Set<T>::insert(const T& item) {if (!member(item)) {rep.push_back(item); // 复用 list 的功能}
}

为什么这样更好?

  1. 接口隔离Set 的用户看不到 std::listpush_frontsplice 等不适合 Set 的函数。
  2. 封装性:以后如果你发现 std::list 性能不好,想换成 std::vector 或自定义的哈希表,你只需要修改 Set 的内部实现 (private 部分),外部使用 Set 的代码完全不需要改动。

4. 总结

  • Public 继承:意味着 "Is-a"(是一个)。除此之外,别用它。
  • Composition(复合):意味着 "Has-a"(拥有)或 "Is-implemented-in-terms-of"(根据某物实现出)。
    • 如果是现实世界的对象关系(如人与地址),它是 Has-a。
    • 如果是纯粹的代码复用(如用 List 实现 Set),它是 Is-implemented-in-terms-of。

在设计类的关系时,优先考虑组合,而不是继承

http://www.jsqmd.com/news/106266/

相关文章:

  • 零基础学会Vue二维码扫描:5分钟快速上手
  • 高效监控利器:vmagent全面解析
  • Vue3中动态样式数组的后项覆盖规则如何与计算属性结合实现复杂状态样式管理?
  • RGBA处理效率对比:传统方法vsAI工具
  • 东方博宜OJ 1222:经典递归问题 —— 汉诺塔
  • 石油化工实验室LIMS系统,石油化工实验室管理系统,LIMS系统实现从原油评价、馏分分析到成品油出厂的全流程质控!
  • Day17 C++提高 之 类模板案例
  • 比手动快10倍!自动化处理Schannel错误的方法
  • AI CRM系统推荐,原圈科技赋能地产销售
  • 用map方法10分钟搭建数据可视化原型
  • 企业数据迁移中Excel格式异常的5个真实案例
  • 代币化资产革命进入2.0阶段:Fasset的“合规密钥”能否解锁万亿级新兴市场?
  • 磁矩表磁计算器
  • 5分钟打造专属VSCode字体主题:在线生成器
  • C# SignalR 添加Swagger
  • 手把手教你复现CVE-2023-51767漏洞
  • 零基础理解神经网络参数:从入门到实践
  • 2025 最新 PPR管 服务商 TOP5 评测!服务深耕四川、贵州、西藏、重庆,优质厂商榜单发布,创新驱动重构给排水管道生态 - 全局中转站
  • JAVA设计模式之观察者模式
  • 零基础HTML速成:用AI写出你的第一个网页
  • 1小时搞定产品原型:HTML+AI快速验证创意
  • Airflow - Postgres Connection
  • DS二叉排序树之创建和插入
  • AI内控智能体开发:把风险防控交给“智能管家”
  • 对比评测:雷柏V500Pro键盘宏编程的3种高效方法
  • 二叉排序树的构建与遍历
  • 专业测评:国产 CRM 中哪些比较适合制造业
  • 无需安装!浏览器直接运行Java8的5种创新方案
  • 分布式锁与幂等的边界——正确的锁语义、过期与续约、业务层幂等配合
  • 2025 最新 PVC管厂家 TOP5 评测!深耕四川、贵州、西藏、重庆,优质服务商权威榜单发布,技术赋能给排水工程新生态 - 全局中转站