读书笔记 effective c++ Item 38 通过组合(composition)为 “has-a”或者“is-implemented-in-terms-of”建模
1. 什么是组合(composition)?
组合(composition)是一种类型之间的关系,这种关系当一种类型的对象包含另外一种类型的对象时就会产生。举个例子:
class Address { ... }; // where someone lives class PhoneNumber { ... };
class Person {
public:
...
private:
std::string name; // composed object Address address; // ditto
PhoneNumber voiceNumber; // ditto PhoneNumber faxNumber; // ditto };
在这个例子中,Person对象由string,Address和PhoneNumber对象组成。对于程序员来说,术语组合(composition)有很多替代词。像分层(layering),包含( containment),聚合 (aggregation), 和植入(embedding)。
2. 组合关系意味着什么?
2.1 区分 “is-a”和“has-a”
Item 32中解释了public继承意味着”is-a”。组合同样有另外一个意思。事实上,有两个意思。组合即意味着“has-a”,也意味着“is-implemented-in-terms-of”。这是因为你正在处理软件中的两种不同领域(domain)。你的程序中的一些对象对应着世界上的真实存在的东西,如果你要为其建模,例如,人类,车辆,视频帧等等。这样的对象是应用域(application
domain)的一部分。其他的对象则是纯实现层面的人工产品。例如,缓存(buffers),互斥信号量(mutexs),搜索树(search
trees)等等。这种类型的对象对应着你软件里的实现域(implementation domain)。当组合关系发生在应用域中的对象之间时,它表示的是“has-a”关系。当发生在实现域中时,它表示的是“is-implemented-in-terms-of”关系。
上面的Person类表示的是一种“has-a”关系。一个Person对象有一个名字,一个地址和一个语音和传真电话号码。你不能说一个人“is-a”名字或者一个人“is-a”地址。你会说一个人“has-a”名字和“has-an”地址。大多数人能够很容易区分这些,因此很少有人会混淆“is-a”和“has-a”的意思。
2.2 区分 “is-a”和“is-implemented-in-terms-of”
更麻烦的是对“is-a”和“is-implemented-in-terms-of”进行区分。举个例子,假设你需要一个类模板表示很小的对象set,也即是没有重复元素的collections。因为重用是美好的事情,你的第一直觉就是使用标准库的set模板。当有一个已经被实现好的模板时你为什么要自己手动实现一个呢?
不幸的是,set的实现对于其中的每个元素都会引入三个指针的开销。因为set通常作为一个平衡搜索树来实现,这能保证将搜索,插入和删除的时间限定在对数级别(logarithmic-time)。当速度比空间重要时,这是个合理的设计,但是对于你的应用,空间比速度要更重要。所以标准库的set没有为你的应用提供正确的权衡。你需要自己实现这个模板。
重用仍然是美好的事情。作为数据结构专家,你知道实现set会有很多选择,其中一个是使用linked lists。你同样知道标准C++库有一个list模板,所以你决定重用它。
特别情况下,你决定让你的初步实现的set模板继承list。也即是Set<T>将会继承list<T>。毕竟,在你的实现中,一个Set对象事实上是一个list对象。所以你将Set模板声明为如下:
template<typename T> // the wrong way to use list for Set class Set: public std::list<T> { ... };
看上去都很好,但事实上有一些地方犯了严重错误。正如Item32中解释的,如果D 是一个B,对于B来说是真的对D来说也是真的。然而,一个list对象可能会包含重复元素,所以如果值3051被插入到Set<int>两次,那么list将会包含3051的两个拷贝。相反。一个Set不可以包含重复元素,所以当3051被插入到Set<int>两次的时候,set只包含一个3051值。现在一个Set是一个List就不再为真了,因为对list对象为真的一些事情对Set对象来说不为真了。
因为这两个类之间的关系不是“is-a”,public继承是为这种关系建模的错误方式。正确的方式是意识到一个Set对象可以被“implemented
in terms of”一个list对象:
template<class T> // the right way to use list for Set class Set { public: bool member(const T& item) const; void insert(const T& item); void remove(const T& item); std::size_t size() const; private: std::list<T> rep; // representation for Set data };
Set成员函数的实现可以依赖list已经提供的功能和标准库的其他部分,所以实现上就简单直接了,前提是你对STL编程的基本知识很熟悉:
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);
}
template<typename T>
void Set<T>::remove(const T& item)
{
typename std::list<T>::iterator it = // see Item 42 for info on
std::find(rep.begin(), rep.end(), item); // “typename” here
if (it != rep.end()) rep.erase(it);
}
template<typename T>
std::size_t Set<T>::size() const
{
return rep.size();
}
这些函数足够简单,它们是inline函数的合理候选人,虽然我知道你想先回顾一下Item30中的讨论之后再做决定。
一些人会争论为了让Set接口更好的符合Item18的建议,也即是设计接口的时候满足容易被正确使用不容易被误用,Set应该遵循STL容器的惯例。但是遵循这些惯例就需要为Set增加很多工作,这会导致list和Set之间的关系模糊不清。既然关系是这个条款的关键点,我们会为了更好的阐述而牺牲STL兼容性。此外,Set接口不应该使Set的无可争辩的正确行为黯然失色:这个权利是它和List之间的关系。这个关系不是”is-a”(虽然一开始看起来像),而是”is-implemented-in-terms-of”。
3. 总结
- 组合和public继承的意义完全不同
- 在应用域中,组合意味了“has-a”,在实现域中,它意味着“is-implemented-in-terms-of”
读书笔记 effective c++ Item 38 通过组合(composition)为 “has-a”或者“is-implemented-in-terms-of”建模的更多相关文章
- 读书笔记 effective c++ Item 39 明智而谨慎的使用private继承
1. private 继承介绍 Item 32表明C++把public继承当作”is-a”关系来对待.考虑一个继承体系,一个类Student public 继承自类Person,如果一个函数的成功调用 ...
- 读书笔记 effective C++ Item 40 明智而谨慎的使用多继承
1. 多继承的两个阵营 当我们谈论到多继承(MI)的时候,C++委员会被分为两个基本阵营.一个阵营相信如果单继承是好的C++性质,那么多继承肯定会更好.另外一个阵营则争辩道单继承诚然是好的,但多继承太 ...
- 读书笔记 effective c++ Item 44 将与模板参数无关的代码抽离出来
1. 使用模板可能导致代码膨胀 使用模板是节省时间和避免代码重用的很好的方法.你不需要手动输入20个相同的类名,每个类有15个成员函数,相反,你只需要输入一个类模板,然后让编译器来为你实例化20个特定 ...
- 读书笔记 effective c++ Item 1 将c++视为一个语言联邦
Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
- 读书笔记 effective c++ Item 19 像设计类型(type)一样设计
1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...
- 读书笔记 effective c++ Item 37 永远不要重新定义继承而来的函数默认参数值
从一开始就让我们简化这次的讨论.你有两类你能够继承的函数:虚函数和非虚函数.然而,重新定义一个非虚函数总是错误的(Item 36),所以我们可以安全的把这个条款的讨论限定在继承带默认参数值的虚函数上. ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记 effective c++ Item 48 了解模板元编程
1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...
随机推荐
- python中文编码问题深入分析(一):字符编码基础
背景:笔者作为一名刚接触python语言的新手,在实际的项目中,遇到过一些中文编码问题,初次遇到这些问题的时候,刚开始显得有些手足无措,也不知从何查起.常言道:有问题,找度娘!当我打开www.baid ...
- swift 运算符快速学习(建议懂OC或者C语言的伙伴学习参考)
昨晚看了swift 的运算符的知识点,先大概说一下,这个点和 c 或者oc 的算运符知识点一样,都是最基础最基础的.其他的最基本的加减乘除就不多说了.注意的有几点点..先说求余数运算: 一 :求余数运 ...
- 基于vue2+vuex2+vue-router+axios+elementUI做的自动化后台模板
github地址:https://github.com/sailengsi/sls-admin 此项目重点突出在架构上模式,这个架构模式,可以让我们在开发中,很方便的拓展与维护,并且可以保持结构清晰的 ...
- BDD框架:behave学习记录
本人学习的时候基本上是按照behave的tutorial教程一步步学习的,这篇文章就当Behave教程的翻译版吧(*^__^*) 嘻嘻--. 1 安装behave 安装好python后 ...
- 20155304田宜楠2006-2007-2 《Java程序设计》第一周学习总结
20155304田宜楠2006-2007-2 <Java程序设计>第一周学习总结 教材学习内容总结 - 浏览教材,根据自己的理解每章提出一个问题 第一章 除了书上提到的开发工具还有什么适合 ...
- 一次MySQL死锁问题解决
一次MySQL死锁问题解决 一.环境 CentOS, MySQL 5.6.21-70, JPA 问题场景:系统有定时批量更新数据状态操作,每次更新上千条记录,表中总记录数约为500W左右. 二.错误日 ...
- 前端学PHP之正则表达式基础语法
前面的话 正则表达式是用于描述字符排列和匹配模式的一种语法规则.它主要用于字符串的模式分割.匹配.查找及替换操作.在PHP中,正则表达式一般是由正规字符和一些特殊字符(类似于通配符)联合构成的一个文本 ...
- linux系统端口查看和占用的解决方案
使用netstat命令查看端口占用情况 netstat -tln 查看什么程序占用端口 netstat -tlnp 过滤端口 netstat -tlnp |grep 8080 杀掉进程 kill - ...
- 使用OpenCL
//函数原型举例 cl_int clGetPlatformInfo(cl_platform_id platform, cl_platform_info param_name, size_t param ...
- Handlebars模板引擎之进阶
取得索引 我想取得索引作为序号这个是常用的.在handlebars也是存在的. 就是使用 @index 来获取索引 {{#each this}} <tr> <td>{{ @in ...