当前位置: 首页 > 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.zskr.cn/news/117229.html

相关文章:

  • 石油化工实验室LIMS系统,石油化工实验室管理系统,LIMS系统实现从原油评价、馏分分析到成品油出厂的全流程质控!
  • AI CRM系统推荐,原圈科技赋能地产销售
  • 比手动快10倍!自动化处理Schannel错误的方法
  • Day17 C++提高 之 类模板案例
  • C# SignalR 添加Swagger
  • JAVA设计模式之观察者模式
  • Airflow - Postgres Connection
  • AI内控智能体开发:把风险防控交给“智能管家”
  • 无需安装!浏览器直接运行Java8的5种创新方案
  • 2025最新CPVC电力管服务商 TOP5 评测!服务深耕四川、贵州、西藏、重庆,优质厂商权威榜单发布,技术赋能构建电力工程安全生态 - 全局中转站
  • 零基础用Vue3打造你的第一个PDF阅读器
  • 2025 最新波纹管厂家 TOP5 评测!服务深度覆盖四川、贵州、西藏、重庆,西南标杆 + 全品类解决方案权威榜单发布,技术赋能基建工程升级 - 全局中转站
  • Item40--明智而审慎地使用多重继承(尽量别用,除非是 Interface 接口类)
  • A860-2020-T301编码器
  • 30秒搭建防火墙状态监控原型
  • Item39--明智而审慎地使用 private 继承
  • 2025年国内正规的工业冷却塔实力厂家哪家靠谱,冷却塔填料/方形横流冷却塔/工业冷却塔/圆形逆流冷却塔/工业冷却塔定做厂家哪家权威 - 品牌推荐师
  • 高危漏洞CVE-2025-54004:WooCommerce前台管理插件权限绕过漏洞剖析
  • AutoHotkey v2 (AHK) windows自动化使用
  • 想做安全副业却纠结方向?漏洞挖掘、技术博客、竞赛奖金实战哪个更适合你?
  • ConvLSTM实战:构建交通流量预测系统
  • 一文帮你总结2025年最新获客系统品牌有哪些,必看! - 品牌策略主理人
  • Conda环境管理:比传统pip快3倍的依赖解决方案
  • Redmi Note 12 Pro Speed-开启开发者选项
  • 利用wan2.1协议快速构建网络通信原型
  • TestDisk数据恢复实战:从分区丢失到文件找回的完整指南
  • 盲盒抽赏小程序开发运营指南:从合规架构到百万盈利的全链路拆解
  • 使用 C# 将 DataTable 和 Excel 数据互转
  • 敏捷协作中的心理测试:消除团队沟通的隐藏BUG
  • 2025搭子经济风口:组局小程序开发落地、盈利模式与风险防控手册