这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系)。这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们可以用公有继承描述:

 class Bird
{
public:
virtual void fly(){cout << "it can fly." << endl;}
}; class Penguin: public Bird
{
// fly()被继承过来了,可以覆写一个企鹅的fly()方法,也可以直接用父类的
}; int main()
{
Penguin p;
p.fly(); // 问题是企鹅并不会飞!
}

但问题就来了,虽然企鹅是鸟,但鸟会飞的技能并不适用于企鹅,该怎么解决这个问题呢?方法一,在Penguin的fly()方法里面抛出异常,一旦调用了p.fly(),那么就会在运行时捕捉到这个异常。这个方法不怎么好,因为它要在运行时才发现问题。

方法二,去掉Bird的fly()方法,在中间加上一层FlyingBird类(有fly()方法)与NotFlyingBird类(没有fly()方法),然后让企鹅继承与NotFlyingBird类。这个方法也不好,因为会使注意力分散,继承的层次加深也会使代码难懂和难以维护。

方法三,保留所有Bird一定会有的共性(比如生蛋和孵化),去掉Bird的fly()方法,只在其他可以飞的鸟的子类里面单独写这个方法。这是一种比较好的选择,因为根本没有定义fly()方法,所以Penguin对象调用fly()会在编译期报错。

在艰难选择方法三之后,我们回过头来思考,就是在所有public继承的背后,一定要保证父类的所有特性子类都可以满足(父类能飞,子类一定可以飞),抽象起来说,就是在可以使用父类的地方,都一定可以使用子类去替换。

这正是Liskov替代原则告诉我们的:任何父类可以出现的地方,子类一定可以替代这个父类,只有当替换使软件功能不受影响时,父类才可以真正被复用。通俗地说,是“子类可以扩展父类的功能,但不能改变父类原有的功能”。

下面再来看一个数学上的例子,对于基础几何里的长方形和正方形,老师会说“正方形是特殊的长方形”,“特殊”体现在长宽是相等的,从字面上来看,我们可以表达成is-a关系,像这样:

 class Rectangle
{
protected:
int length;
int width;
public:
void virtual IncreaseLength(int DeltaLength)
{
length += DeltaLength;
}
}; class Square: public Rectangle
{
public:
void virtual IncreaseLength(int DeltaLength)
{
length += DeltaLength;
width += DeltaLength;
}
};

为了与保持正方形长宽等值的特性,不得不将IncreaseLength里面也对width进行了操作,到这一步,恐怕成员函数名IncreaseLength已经变味了,明明是扩展长度,但“偷偷地”也把宽度给变了。这就与“子类可以扩展父类的功能,但不能改变父类原有的功能”相背离了,所以这个is-a关系在数学世界里也可这样说说,但在程序的世界里并不成立!

举上面两个例子,足可以看出public继承并不像口头上说的那么容易,要去理解两个类是否可以真正成为is-a关系,还需要好好斟酌。最后总结一下:

“public继承”意味着is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。

读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系的更多相关文章

  1. 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承

    private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...

  2. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  3. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  4. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  5. 读书笔记_Effective_C++_条款四十二:了解typename的双重意义

    顾名思义,typename有双重含意.只要你用过template,那么第一重含意一定知道,那就是声明模板的时候,我们既可以这样写: template <class T> 也可以这样写 te ...

  6. 读书笔记_Effective_C++_条款三十八:通过复合塑模出has-a或者is-implemented-in-terms-of

    如果说public是一种is-a的关系的话,那么复合就是has-a的关系.直观来说,复合就是在一个类中采用其他类的对象作为自身的成员变量,可以举个例子,像下面这样: class Person { pr ...

  7. 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...

  8. 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型

    比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...

  9. 读书笔记_Effective_C++_条款四十九:了解new_handler的行为

    本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete.在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常.有的时候,我们希望能 ...

随机推荐

  1. 2018ICPC南京网络赛

    2018ICPC南京网络赛 A. An Olympian Math Problem 题目描述:求\(\sum_{i=1}^{n} i\times i! \%n\) solution \[(n-1) \ ...

  2. 二十一、springboot之定制URL匹配规则(项目中遇到的问题:get方式传参,带有小数点,被忽略)

    一.问题描述: get方式传参,在传送价格,积分时(带有小数点),debug后台微服务接受到的参数,却不带小数点,如:price是0.55,后台接受后却是0 二.解决 在WebConfiguratio ...

  3. 转载: Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

    阿里的UI库... 分析的很精辟... http://blog.csdn.net/carson_ho/article/details/71077193

  4. Python模块制作

    在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字. 定义自己的模块 比如有这样一个文件test.py,在test.py中定义了函数add def add(a,b): ...

  5. [转] Cacti+Nagios监控平台完美整合

    Cacti+Nagios监控平台完美整合 http://os.51cto.com/art/201411/458006.htm 整合nagios+cacti+微信.飞信实现网络监控报警 http://b ...

  6. IntelliJ IDEA 自动导入包的问题

    我们再使用IDE写代码的时候,往往需要 鼠标点中这个类 然后 使用 alt+enter ,导入响应的包,如果导入的包比较多,一个一个点 也是费事.  因为用手动,有可能需要你选择导入那个包,有时候类名 ...

  7. web文件<async-supported>错误分析

    <async-supported>true</async-supported> 出现 cvc-complex-type.2.4.a: Invalid content was f ...

  8. XML文件解析-SaxReader

    一.前言 java解析xml文件有几种方式,这里介绍一下用SaxReader来解析Xml的方法. 二.准备 如果用SaxReader的话,需要引入jar包dom4j, 版本的话官网下载一个就好,这里用 ...

  9. 转发:RocketMQ与kafka的对比

    淘宝内部的交易系统使用了淘宝自主研发的Notify消息中间件,使用Mysql作为消息存储媒介,可完全水平扩容,为了进一步降低成本,我们认为存储部分可以进一步优化,2011年初,Linkin开源了Kaf ...

  10. day4 计算器

    作业:计算器开发 (1)实现加减乘除及拓号优先级解析: (2)用户输入 1 - 2 * ( (60-30 +(-40/5) * (-9-2*5/-3 + 7 /3*99/4*2998 +10 * 56 ...