继承是面向对象程序设计的重要特性之一。作为面向对象的编程语言,C++语言也自然支持这个特性。继承是代码复用的基本方法之一,也是接口和复用设计的关键。本文介绍继承的含义和继承与组合的关系。
01、继承的含义
面向对象程序设计通过将问题域中的事物抽象成类来实现问题空间到程序空间的映射,从而简化问题的求解。然而,问题域中的事物并不处于同一个层次,往往可根据概念的抽象层次分为多层,比如汽车、火车、飞机等可抽象成更高层次的运输工具,而鸟、鱼、哺乳动物等可抽象成更高层次的动物。在抽象的不同层次中,抽象的高层具有的特征在抽象的低层都会体现,反之则不然。这个关系称之为继承关系。在继承关系中,称被继承的类为基类(或父类),称通过继承方式产生的新类为派生类(或子类、导出类)。例如运输工具可以作为基类,而汽车、火车和飞机则是从运输工具派生出的三个派生类,从它们还可以派生出许多的类。运输工具的部分层次结构如图1所示。
▋图1 运输工具的部分层次结构
通常,当一个类提供的功能不能满足新的要求,但该类提供的功能均可被重用且在概念上具有继承关系时,就会在该类的基础上使用继承机制从该类派生出新的类,并为新的类增加新的功能以满足要求。这样,不仅可以重复使用原有的、可靠的代码,减少工作量、增加工作效率,而且在程序的演化上也比较清楚,容易控制。
面向对象程序设计中的继承与现实中的继承有很大相关性,并且现实中的继承关系或逻辑系统中的继承关系可以帮助确定面向对象程序设计中的继承关系,但两者并不总是一致。例如,在逻辑上,圆是椭圆的一个特例,似乎椭圆是更高一层的抽象,然而在面向对象程序设计中,这是不合理的,因为椭圆中的长轴和短轴的概念对圆来说是没有意义的。总之,在面向对象程序设计中,需要合理使用继承关系,不能滥用。
组合也是一种代码重用的方法,它意味着组合类中的某些数据成员是其他已有类的对象。实际上,一直以来都在用组合的方式创建类,只不过是在用内部数据类型来组合新类。其实,使用用户自定义的类来组合新的类也很容易。
如果两个类间有继承关系,则意味着两个类间有“是一个”关系,并且较抽象的类的特性均适用于较一般的类。在实际使用中,到底选择继承还是组合,关键是分析类间的关系:如果类间有“是一个”关系,则需要考虑使用继承;如果有“整体-部分”(或“是...的一部分”)关系,则需要考虑使用组合。在此基础上还需要考虑基类的特性是否在派生类中均有意义。
02、继承与组合
通过继承可以节约编程量,例如在CBird类的print()函数的实现中,为了输出从基类继承下来的name和lifespan成员,仅需要调用基类的print()函数,这样既有很好的封装性,又节约了编程工作量。又如,在CBird类中,speak()函数可以直接从基类CAnimal继承,而不需要再次实现。
组合也是节约编程量的一种方法。那么什么时候使用继承、什么时候使用组合呢?这就需要考虑两个类之间的关系。当两个类间有“是一个”关系时,可以考虑使用继承,例如前面讲过的CBird类和CAnimal类;当两个类间有“有一个”关系,更准确地说有“是…的一部分”的关系时,需要考虑使用组合关系。
在实际使用中,组合关系常被误用为继承关系。为进一步明确组合与继承的不同,考虑下面这个情况:假设有一个圆类Circle,其有表示半径的数据成员,提供计算周长和面积的公有函数,并在此基础上设计圆柱体类Cylinder。类Circle的具体定义如下:
class Circle
{
public:
Circle(double r = 0) : radius(r) { }
double circumference() { return 2 * 3.14 * radius; }
double area() { return 3.14 * radius * radius; }
double get_radius() { return radius; }
private:
double radius;
};
如果现在要定义一个圆柱体Cylinder,则需要考虑圆柱体是在圆的基础上加上一个高构成,并且圆非圆柱,圆只是构成描述圆柱体的一部分而已,因此应使用组合来设计圆柱体类,具体定义如下:
class Cylinder
{
public:
Cylinder(Circle c, double h) : btm(c), height(h) { }
Circle bottom() { return btm; }
double volume() { return btm.area() * height; }
private:
Circle btm;
double height;
};
当然,从功能上来说,可以通过继承来设计圆柱体类,即从Circle派生并添加一个高度成员,但这种设计思想是错误的,是对继承关系的滥用。示例程序如下:
class Cylinder : public Circle
{
public:
Cylinder(double r, double h) : Circle(r), height(h) { }
Circle bottom() { return *this; }
double volume() { return this->area() * height; }
private:
double height;
};