Effective C++条款38:通过复合塑模出 has-a 或“根据某物实现出“

Effective C++条款38:通过复合塑模出 has-a 或"根据某物实现出"(Model "has-a" or "is-implemented-in-terms-of" through composition)


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:

第6章:继承与面向对象设计

在这里插入图片描述


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

1、什么是复合(composition)?

  复合(composition)是类型之间的一种关系,这种关系当一种类型的对象包含另外一种类型的对象时就会产生。例如:

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

  在这个例子中,Person对象由string,Address和PhoneNumber对象构成。在程序员之间,复合(composition)有很多同义词。像分层(layering),包含( containment),聚合 (aggregation), 和植入(embedding)。

2、“is-a”和“has-a”

  条款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”的意思。

3、“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将会继承list。毕竟,在你的实现中,一个Set对象事实上是一个list对象。所以你将Set模板声明为如下:

template<typename T>                     
class Set: public std::list<T> {
    
     ... };     

  每件事看上去都很好,但实际上有些东西完全错误。正如条款32中解释的,如果D 是一个B,对于B来说是真的对D来说也是真的。然而,一个list对象可能会包含重复元素,所以如果值3051被插入到Set两次,那么list将会包含3051的两个拷贝。相反。一个Set不可以包含重复元素,所以当3051被插入到Set两次的时候,set只包含一个3051值。现在一个Set是一个List就不再为真了,因为对list对象为真的一些事情对Set对象来说不为真了。

  因为这两个类之间的关系不是“is-a”,public继承是为这种关系塑模的错误方式。正确的方式是意识到一个Set对象可以被“implemented in terms of”一个list对象:

template<class T>                           
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成员函数的实现可以依赖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函数的合理候选人。

3、牢记

  • 复合的意义和public继承完全不同。

  • 在应用域,复合意为has-a(有一个)。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)。

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

猜你喜欢

转载自blog.csdn.net/CltCj/article/details/128682696