zoukankan      html  css  js  c++  java
  • 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

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

     1 class Bird
     2 {
     3 public:
     4     virtual void fly(){cout << "it can fly." << endl;}
     5 };
     6 
     7 class Penguin: public Bird
     8 {
     9     // fly()被继承过来了,可以覆写一个企鹅的fly()方法,也可以直接用父类的
    10 };
    11 
    12 int main()
    13 {
    14     Penguin p;
    15     p.fly(); // 问题是企鹅并不会飞!
    16 }

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

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

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

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

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

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

     1 class Rectangle
     2 {
     3 protected:
     4     int length;
     5     int width;
     6 public:
     7     void virtual IncreaseLength(int DeltaLength)
     8     {
     9         length += DeltaLength;
    10     }
    11 };
    12 
    13 class Square: public Rectangle
    14 {
    15 public:
    16     void virtual IncreaseLength(int DeltaLength)
    17     {
    18         length += DeltaLength;
    19         width += DeltaLength;
    20     }
    21 };

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

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

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

  • 相关阅读:
    java:输出流程printStream
    phalcon 连接多个数据库 phalcon multi-database
    Selenium Webdriver元素定位的八种常用方法
    adb push ,adb pull和adb install的区别
    Java将数据写进excel
    Java接口和抽象类的区别
    深入理解Java的接口和抽象类
    Java内存解析 程序的执行过程
    bit,byte,char,位,字节,字符 的区别
    java static成员变量方法和非static成员变量方法的区别 ( 二 )
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/3556715.html
Copyright © 2011-2022 走看看