[Effective C++]条款32 public继承

本文初发于 “天目中云的小站”,同步转载于此。

条款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 天目中云

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值