条款38:通过复合塑模出has-a或"根据某物实现出"
复合是类型之间的一种关系,当某种类型的对象内包含其他类型的对象,我们说这是一种复合。
public继承是is-a关系,表示是一种的意思。复合意味着has-a关系,表示有一个或者is-implemented-in-terms-of(根据某物实现出)。
现在假设你需要一个template制造出一组classes表示不重复的对象组成的sets。你第一个想到的就是set容器。但是set容器考虑的是平衡,现在你考虑的是空间比效率更主要。所以你的底层选作List,那么是继承好呢,还是复合好呢?
先看继承:
template<typename T>
class Set:public std::List<T>{........};
我们已经很清楚的知道了public继承表示的is-a关系。也就是Set是一个List。但是List是可以插入相同元素的。这与public继承不相符合。
所以好的方法是复合
template<class T>
class Set
{
public:
//各种接口
.........
private:
std::list<T> rep;
};
条款39:明智而审慎的使用private继承
private继承是一种纯粹的实现技术,意思就是感觉某物实现的意思。他在软件设计的层面上是没有意义的。主要在软件实现层面上的意义。那么private继承与复合需要如何取舍呢?我们应该尽可能使用复合,必要的时候使用private继承。什么时候是必要的?主要是protected成员和virtual函数牵涉进来的时候。
当你面对"并不存在is-a关系"的两个类,其中一个类需要访问另外一个类的protected成员或者需要重新定义一个或多个virtual函数,private继承会是很好的选择。
条款40:明智而审慎的使用多继承
多继承的歧义:
class Borrow
{
public:
void check();
....
};
class Elect
{
private:
bool check();
....
};
class MP3:public Borrow,public Elect
{
.....
};
MP3 pm3;
mp3.check();// 歧义
我们可以指定基类:mp3.Borrow::check();
多重继承容易导致钻石形状,如C++的文件对象就是如此
class File{...};
class InputFile: public File {...};
class OutputFile: public File{...};
class IOFile: public InputFile,
public OutputFile
{...};
class File{...};
class InputFile: virtual public File {...};
class OutputFile: virtual public File{...};
class IOFile: public InputFile,
public OutputFile
{...};
从正确行为来看,public继承应该总是virtual,用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes成员变量速度慢。
下面看下C++ interface的例子:
class IPerson{
public:
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const =0;
};
//根据一个独一无二的数据库ID创建一个Person对象
std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);
DatabaseID askUserForDatabaseID();
DatabaseID id(askUserForDatabaseID());
std::tr1::shared_ptr<IPerson> pp(makePerson(id));
CPerson继承自IPerson, 另外有个PersonInfo类提供CPerson的实质东西
class PersonInfo{
public: explicit PersonInfo(DatabaseID pid);
virtual ~PersonInfo();
virtual const char* theName()const;
virtual const char* theBirthDate() const;
private:
virtual const char* valueDelimOpen() const;
virtual const char* valueDelimClose() const;
};
PersonInfo::theName的代码看起来像这样:
const char* PersonInfo::valueDelimOpen() const
{
return "[";//default
}
const char* PersonInfo::valueDelimClose() const
{
return "]";//default
}
const char* PersonInfo::theName() const
{
//保留缓冲区给返回值使用:static,自动初始化为“全0”
static char value[Max_Formatted_Field_Value_Length];
//写入起始符号
std::strcpy(value, valueDelimOpen());
将value内的字符串附到这个对象的name成员变量中
//写入结尾符号
std::strcat(value, valueDelimClose());
return value;
}
所以theName返回的结果不仅仅取决于PersonInfo也取决于从PersonInfo派生下去的classes。
Cperson和personInfo的关系是,PersonInfo刚好有若干函数可帮助Cperson比较容易实现出来。因此它们的关系是is-implemented-in-term-of。这种关系可以两种技术实现:复合和private继承。一般复合必要受欢迎,本例之中Cperson要重新定义valueDelimOpen和valueDelimClose,所以直接的解法是private继承。
Cperson还有必须实现Iperson的接口,那得要public继承才能完成。这导致多重继承的一个通情达理的应用:将“public继承自某接口”和“private继承自某实现”结合在一起:
class Cperson: public IPerson,private PersonInfo{
public:
explicit Cperson(DatabaseID pid): PersonInfo(pid){}
virtual std::string name() const
{
return PersonInfo::theName();
}
virtual std::string birthDate() const
{
return PersonInfo::theBirthDate();
}
private:
const char* valueDelimOpen() const {return "";}
const char* valueDelimClose() const {return "";}
};
如果你唯一能提出的设计涉及多重继承,你应该再努力想一想----几乎可以说一定会有某些方案让单一继承行的通。然而有时候多继承的确是完成任务最简洁、最易维护、最合理的做法,就别害怕使用它。只是确定,的确在明智而审慎的情况下使用它。