🧩 第二章 似是而非:面向对象与现实世界
📄 一句话概括

企鹅是鸟,但鸟都会飞不成立;最糟的是继承下来之后不报错,只是逻辑错了。

📝 本节和前面几节的关系

2.5 说比喻会歪,这一节给出最典型的例子——这是本章标题的正题。

一、企鹅这个反例

class Penguin(Bird),Bird 里有 fly。 实测 isinstance(p, Bird) 是 True——现实里企鹅确实是鸟。 实测 p.fly() 返回 【飞起来了】——错得很彻底,但程序不报错。 「企鹅是鸟」成立,「鸟都会飞」不成立。两句话常被当成一句话用。

二、为什么它不报错

因为继承只检查类型关系,不检查能力是否成立。 Penguin 是 Bird 的子类,所以它【有】fly 这个方法——至于这个方法对它有没有意义,编译器不管。 最糟的就是这种错:不报错,只是逻辑是错的。它会在某个业务场景里悄悄出问题。

三、分类与能力是两回事

分类(鸟/哺乳动物)回答【它是什么】。 能力(会飞/会游泳)回答【它能做什么】。 现实里这两样常常一致,但软件里必须分开表达: 会飞的实现一个 fly 接口,不会飞的不实现。分类管分类,能力管能力。

四、更麻烦的一类

员工是人,成立;人都会休假,不成立(合同工、外包、实习生规则都不同)。 凡是【都会…】的句子,都要停下来验一遍。 验法很简单:找一个反例。找得到,就不能写进父类。

五、动手 Demo:面向对象和现实世界是似是而非的

让企鹅继承鸟,然后调一次 fly。 看它返回什么、会不会报错、以及 isinstance 怎么说。

class Bird:
    def fly(self): return "飞起来了"

class Penguin(Bird):
    pass

p = Penguin()
print("是鸟吗  ", isinstance(p, Bird))
try:
    print("fly()  ", p.fly())
    print("报错吗  没有")
except Exception as e:
    print("报错    ", type(e).__name__)

实跑输出:

--------------------------------------------------------------------
面向对象和现实世界是似是而非的
--------------------------------------------------------------------

  【似是而非】四个字,最经典的例子是**企鹅**。

      class Penguin(Bird): pass
      p = Penguin()

  表达式                  实测结果     对照现实
  --------------------------------------------------------
  isinstance(p, Bird)     True         现实里:企鹅是鸟
  p.fly()                 飞起来了     现实里:企鹅不会飞
  报错了吗                没有         这才是最糟的

  **最糟的就是第三行:不报错,只是逻辑是错的。**
  现实里的分类(鸟)和软件里的能力(会飞)**不是一回事。**

  现实里的说法     软件里成立吗     为什么
  -------------------------------------------------------
  企鹅是鸟         成立             分类关系对
  鸟都会飞         不成立           能力不能靠分类继承
  员工是人         成立             分类关系对
  人都会休假       看情况           合同工和正式工不一样

  **正解是把【能力】单独拎出来**:会飞的一类实现 `fly`,不会飞的不实现。
  **分类管分类,能力管能力,别用一根继承线串起来。**

实测:是鸟=True,fly() 照样返回【飞起来了】,不报错。 最糟的就是这种:不报错,只是逻辑是错的。

30 秒实验

  1. 找一个你写过的【都会…】的父类方法,看看有没有反例。
  2. 把企鹅的例子改成接口,看调用方会多写什么、换来什么。

常见误区

  1. 以为现实里的分类能直接当继承用。分类管是什么,能力管能做什么。
  2. 以为不报错就是对的。逻辑错比语法错更难查。
自测三问(点开对答案) 1. 企鹅这个例子说明什么? —— 【企鹅是鸟】成立,【鸟都会飞】不成立;继承只检查类型关系,不检查能力是否成立。 2. 为什么这种错最难查? —— 因为它不报错,程序照常跑,只是逻辑错了,会在某个业务场景里悄悄出问题。 3. 怎么处理分类和能力的冲突? —— 把能力单独拎出来做接口:会飞的实现 fly,不会飞的不实现;分类管分类,能力管能力。

相关笔记