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

继承只做两件事:共同点往上提、不同点往下放;子类没写的方法是顺着链往上找到的,不是复制的。

📝 本节和前面几节的关系

2.3 讲横向协作,这一节讲纵向分类——三大要素里的第三根柱子。

一、上提与下放

矩形和圆都叫【形状】,都有一个名字——这就是共同点,提到 Shape 里写一遍。 面积算法不一样——这就是不同点,下放到各自的 area 里。 继承的动作就这么两个,没有第三个。

二、MRO:找方法的顺序

实测 Rect.__mro__ 是 Rect → Shape → object。 这说明:子类没写的方法,是顺着这条链往上找到的,不是复制一份下来的。 所以 Rect().describe() 能跑——它用的是 Shape 那份。 这一条直接决定了下一个结论。

三、改父类,子类一起变

因为方法是【往上找】的,改 Shape.describe,所有子类同时变。 好处是省事,坏处是危险——父类一动,所有子类的行为都跟着动,可能谁都没意识到。 这就是为什么很多团队规定【父类尽量稳】——动它等于动全家族。

四、继承 vs 组合

继承说的是【是一种】(is-a):矩形是一种形状。 组合说的是【有一个】(has-a):订单有一个顾客。 最容易搞错的一条:只想复用几行代码,别用继承——用组合。 因为继承会把两个类的生命周期绑在一起,而你可能只是想要那几行代码。

五、动手 Demo:继承对共同点和不同点进行系统的分类和整理

把共同点提到父类、不同点留在子类,再看 Python 找方法的顺序。 MRO 会告诉我们:子类没写的方法到底是从哪儿来的。

class Shape:
    def __init__(self, name): self.name = name
    def describe(self): return self.name

class Rect(Shape):
    def __init__(self, w, h):
        Shape.__init__(self, "矩形")
        self.w, self.h = w, h
    def area(self): return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        Shape.__init__(self, "圆")
        self.r = r
    def area(self): return 3.14159 * self.r * self.r

for c in (Rect, Circle):
    print(c.__name__, [x.__name__ for x in c.__mro__])
print(Rect(6, 4).describe(), Circle(3).describe())

实跑输出:

--------------------------------------------------------------------
继承对共同点和不同点进行系统的分类和整理
--------------------------------------------------------------------

  继承的动作只有两个:**把共同点往上提,把不同点往下放。**

      class Shape:   name / describe()      ← 共同点
      class Rect:    area()  = w * h        ← 不同点
      class Circle:  area()  = π * r * r    ← 不同点

  实测方法解析顺序(MRO)——Python 找一个方法时按什么顺序翻:

      Rect   →   Rect → Shape → object
      Circle →   Circle → Shape → object

  这解释了一条常被忽略的事:**子类没写的方法,是【顺着链路往上找】找到的**,
  不是【复制一份下来】。**所以改父类的方法,所有子类一起变。**

  实测确认一下这个【一起变】:
      Rect().describe()   → 矩形
      Circle().describe() → 圆
  两个子类都没写 `describe`,**是 Shape 那份在起作用。**

                 继承               组合
  --------------------------------------------------
  关系说法       是一种(is-a)     有一个(has-a)
  改父类影响     所有子类跟着变     只影响自己
  什么时候用     共同点真一样       只想复用点代码

  **最后一行最容易搞错**:只想复用几行代码,别用继承——用组合。

实测 MRO:Rect → Shape → object、Circle → Shape → object。 两个子类都没写 describe,是 Shape 那份在起作用——方法是往上找的,不是复制的。

30 秒实验

  1. 在父类里改一句 describe,看两个子类的输出是不是一起变。
  2. 把继承改成组合,看看代码会多几行、好处在哪。

常见误区

  1. 以为继承是把父类代码复制下来。是顺着链往上找。
  2. 为了复用几行代码就用继承。那是组合的活儿。
自测三问(点开对答案) 1. 继承只做哪两件事? —— 把共同点往上提,把不同点往下放。 2. MRO 说明了什么? —— 子类没写的方法不是复制下来的,而是顺着 Rect → Shape → object 这条链往上找到的。 3. 什么时候不该用继承? —— 只是想复用几行代码时——用组合;继承会绑住两个类的生命周期。

相关笔记