上面的 Person class(类)示范了 has-a(有一个)的关系。一个 Person object(对象)has a(有一个)名字,一个地址,以及语音和传真电话号码。你不能说一个人 is a(是一个)名字或一个人 is an(是一个)地址。你可以说一个人 has a(有一个)名字和 has an(有一个)地址。大多数人对此区别不难理解,所以混淆 is-a和 has-a(有一个)之间的角色的情况非常少见。
is-a和 is-implemented-in-terms-of(是根据……实现的)之间的区别稍微有些棘手。例如,假设你需要一个类的模板来表现相当小的 objects(对象)的 sets,也就是说,排除重复的集合。因为 reuse(复用)是一件受人欢迎的事情,你的第一个直觉就是使用标准库中的 set template(模板)。当你能使用已经被写好的东西时,为什么还要写一个新的 template(模板)呢?
reuse(复用)依然是一件受人欢迎的事情。作为 data strUCture(数据结构)的专家,你知道实现 sets 的诸多选择,其中一种是使用 linked lists(线性链表)。你也知道标准的 C++ 库中有一个 list template(模板),所以你决定(复)用它。
具体地说,你决定让你的新的 Set template(模板)从 list 继续。也就是说,Set<T> 将从 list<T> 继续。究竟,在你的实现中,一个 Set object(对象)实际上就是一个 list object(对象)。于是,你就像这样声明你的 Set template(模板):
template<typename T> // the wrong way to use list for Set class Set: public std::list<T> { ... }; 在这里,看起来每件事情都很好。但实际上有一个很大的错误。就像《C++箴言:确保公开继续模拟“is-a”》中的解释,假如 D is-a(是一个)B,对于 B 成立的每一件事情对 D 也成立。然而,一个 list object(对象)可以包含重复,所以假如值 3051 被插入一个 list<int> 两次,那个 list 将包含 3051 的两个拷贝。与此对照,一个 Set 不可以包含重复,所以假如值 3051 被插入一个 Set<int> 两次,那个 set 只包含该值的一个拷贝。因此一个 Set is-a(是一个)list 是不正确的,因为对 list objects(对象)成立的某些事情对 Set objects(对象)不成立。
因为这两个 classes(类)之间的关系不是 is-a(是一个),public inheritance(公有继续)不是模拟这个关系的正确方法。正确的方法是熟悉到一个 Set object(对象)可以 be implemented in terms of a list object(是根据一个 list 对象实现的):
template<class T> // the right way to use list for Set class Set { public: bool member(const T& item) const;
private: std::list<T> rep; // representation for Set data }; Set 的 member functions(成员函数)可以极大程度地依靠 list 和标准库的其它部分已经提供的机能,所以只要你熟悉了用 STL 编程的基本方法,实现就非常简单了: