本文初发于 “天目中云的小站”,同步转载于此。
条款32 : 确定你的public继承塑膜出 is-a 关系
从本条款开始我们将步入面向对象编程这一重要领域进行讨论, 首先我们将会围绕public继承, 分析public继承的意义与目的, 再举出两个错误的public继承典例.
public继承意味"is-a"(是一种)的关系
这便是C++面向对象编程的最重要的一个规则, 请把这个规则牢牢记在心中吧, 相信很多人以及对这种认知有所耳闻, 我甚至在学java的多态时都听到过这个概念, 可见此思想影响之深远.
如果你令B公开继承自A, 那么代表着B是一种A, 进而代表着所有可以使用A的场景都可以使用B, 所有A可以产生的行为B都可以产生. 反之则不然, 需要B的场景并不能用A替代.
这种规则表面上很容易理解, 但是我们应当多去站在语法和程序本身的角度去理解, 因为我们在生活中的直觉可能会误导我们, 它并不一定是富有逻辑性而且缜密, 这会让我们想当然地去认为xxx是一种xxx.
让我们通过两个例子来揭露这种错误.
鸟会飞?
直觉来看, 鸟应该是会飞的, 但是当我们想到鸵鸟和企鹅, 事情就开始不对劲了, 我们先来看第一版 :
class Bird {
public:
virtual void fly(); // 鸟会飞
...
};
class Penguin:public Bird { // 企鹅是鸟
...
};
这里我们可以看出, 企鹅确实是鸟, 但企鹅确实不会飞, 它本就不应该继承fly()函数, 于是我们就会有下一版 :
class Bird {
... // 不在声明fly()函数
};
class FlyingBird: public Bird { // 一个新的派生类, 用来做所有飞鸟的基类
public:
virtual void fly();
...
};
class Penguin: public Bird {
... // 这里就继承不到fly()了
};
至此逻辑就合理了, 这也在告诫我们应当仔细分析设置在类中的每个成员函数是否合理.
进一步梳理
上一个例子因为鸟是否会飞的需求而产生了不同的设计, 但是书中想告诉我们, 世界上并不存在一个"适用于所有软件"的完美设计, 因为时间在流逝, 需求在变化, 我们的设计一定要符合需求, 假如我们整个体系就是针对飞鸟来建立的, 完全不会与企鹅鸵鸟相关, 那么最开始的设计完全是可行的. 简单来说, 我们要明确需求, 根据需求分析各种不同的情况, 做出符合当前需求的继承设计, 当然也可以有一定的前瞻性, 但不要与当下的需求相悖.
正方形是一种矩形?
这相比于上一例中由于常识产生的错误不同, 这就是数理方面的真理, 但从最终结果上并不适合public
继承, 我们来看代码 :
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const; // return current values
virtual int width() const;
void addWidth() // 调用这个函数会使这个width增加10, 并且height不加
{
int oldHeight = height();
setWidth(width() + 10);
assert(height() == oldHeight); // 检察height是否变化
}
...
};
class Square: public Rectangle {...};
当我们用Square
写出如下代码就会发生问题 :
Square s;
assert(s.width() == s.height()); // 看正方形的性质是否保持
s.addWidth(); // 调用addWidth
assert(s.width() == s.height()); // 看正方形的性质是否保持
最后一条assert
将会触发, 原因很容易理解, addWidth
只增加了宽度, 这打破的正方形的性质.
进一步分析, addWidth()
这个函数本身是完全合理的, 因为一个矩形就是可以只增加宽度而不增加长度, 错的是自顾自public
继承自它的Square
, 因为它假定了正方形是一种矩形, 尽管这在公理上确实是成立的, 但是我们要求的是正方形可以做出所有矩形可以做出的行为, 这仿佛就不行了, 因为矩形可以只增加宽度而正方形不可以.
尾声
is-a(是一种)
并非是唯一存在于继承中关系, 我们还会再条款38和条款39讨论另外两个常见的关系, has-a(有一个)
和is-implemented-in-terms-of(根据某物实现出)
, 而我们上文正方形和矩形的关系便可以用has-a
来解释. 在学完另外两种关系之后, 我们便应当好好了解这些关系之间的差异, 在继承中做到正确的选择.
请记住 :
public
继承意味"is-a
"(是一种)的关系, 若B继承自A, 所有可以使用A的场景都可以使用B, 所有A可以产生的行为B都可以产生.- 不要太相信直觉, 当你认为某些事物有 "
is-a
"的关系, 不妨再看看是否符合上一条的后半句.
by 天目中云