通过复合塑模出has-a或“根据某物实现出”——条款38

        复合(composition)是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。例如:

class Address { ... };    // 某人的住址
class PhoneNumber { ... };
class Person {
public:
	...
private:
	std::string name;             // 合成成分物(composed object)
	Address address;              // 同上
	PhoneNumber voiceNumber;      // 同上
	PhoneNumber faxNumber;        // 同上
};

        本例之中Person对象由string, Address, PhoneNumber构成。在程序员之间复合(composition)这个术语有许多同义词,包括layering(分层),containment(内含),aggregation(聚合)和embedding(内嵌)。

        条款32曾说,“public继承”带有is-a(是一种)的意义。复合也有它自己的意义。实际上它有两个意义。复合意味着has-a(有一个)或is-implemented-in-terms-of(根据某物实现出)。那是因为你打算在你的软件中处理两个不同的领域(domains)。程序中的对象其实相当于你所塑造的世界中的某些事物,例如人、汽车、一张张视频画面等等。这样的对象属于应用域(application domains)部分。其它对象则纯粹是实现细节上的人工制品,像是缓冲区(buffers)、互斥器(mutexes)、查找树(search trees)等等。这些对象相当于你的软件的实现域(implementation domain)。当复合发生于应用域内的对象之间,表现出has-a的关系;当它发生于实现域内则是表现is-implemented-in-terms-of的关系。

        上述的Person class示范has-a关系。Person有一个名称,一个地址,以及语音和传真两笔电话号码。你不会说“人是一个名称”或“人是一个地址”,你会说“人有一个名称”和“人有一个地址”。大多数人接受此一区别毫无困难,所以很少人会对is-a和has-a感到困惑。

        比较麻烦的是区分is-a(是一种)和is-implemented-in-terms-of(根据某物实现出)这两种对象的关系。假设你需要一个template,希望制造出一组classes用来表现由不重复对象组成的sets。由于复用(reuse)是件美妙无比的事情,你的第一个直觉是采用标准程序库提供的set template。是的,如果他人所写的template合乎需求,我们何必另写一个呢?

        不幸的是set的实现往往招致“每个元素耗用三个指针”的额外开销。因为sets通常以平衡查找树(balanced search trees)实现而成,使它们在查找、插入、移除元素时保证拥有对数时间(logarithmic-time)效率。当速度比空间重要,这是个通情达理的设计,但如果你的程序却是空间比速度重要呢?那么标准程序库的set提供给你的是个错误决定下的取舍。似乎你终究还得写个自己的template。

        当容我再说一次,复用(reuse)是件美好的事。如果你是一位数据结构专家,你就会知道,实现sets的方法太多了,其中一种便是在底层采用linked lists。而你又刚好知道,标准程序库有一个list template,于是你决定复用它。

        更明确地说,你决定让你那个萌芽中的Set template继承std::list。也就是让Set<T>继承list<T>。毕竟在你的实现理念中Set对象其实是个list对象。你于是声明Set template如下:

template<typename T>                      // 将list应用于Set。错误做法。
class Set: public std::list<T> { ... }; 

        每件事看起来都很好,但实际上有些东西完全错误。一如条款32所说,如果D是一种B,对B为真的每一件事情对D也都应该为真。但list可以内含重复元素,如果数值3051被安插到Set<int>两次,这个Set只内含一笔3051.因此“Set是一种list”并不为真,因为对list对象为真的某些事情对Set对象并不为真。

        由于这两个classes之间并非is-a的关系,所以public继承不适合用来塑模它们。正确的做法是,你应当了解,Set对象可根据一个list对象实现出来:

template<class T>                       // 将list应用于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;                  // 用来表述Set的数据
};

        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 = std::find(rep.begin(), rep.end(), item);  // 见条款42对“typename”的讨论
	if (it != rep.end()) {
		rep.erase(it);
	}
}
template<typename T>
std::size_t Set<T>::size() const
{
	return rep.size();
}

        这些函数如此简单,因此都适合成为inlining候选人。但请记住,在做出任何与inlining有关决定之前,应该先看看条款30

请记住

  • 复合(composition)的意义和public继承完全不同。
  • 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值