⌨️ 第五章 从设计到测试
📄 本篇摘要

多态(polymorphism)=同一个方法名,不同类各自实现——调用方只管喊 speak(),猫喵狗汪各答各的。接口=一份方法名清单(契约),谁签了谁必须实现。多态是【三大特性】的集大成者:让调用方与具体类型解耦。

📝 关系

上一节:封装与访问修饰符 | 下一节:UML 与设计模式

一、多态:同一声令下,各自行动

for animal in [Dog("旺财"), Cat("咪咪"), Robot("R2")]:
    print(animal.speak())     # 同一行代码,三种结果

调用方完全不知道(也不需要知道)列表里装的是什么——只知道它们都会 speak()。加一个新动物 Duck,这一行循环一个字都不用改。这就是多态的威力:新增类型不改旧代码(开闭原则的日常形态)。

二、接口:一份契约

接口(interface)只声明【有哪些方法】,不写实现:

接口的意义:把【能做什么】与【怎么做】分离——调用方依赖接口(能做什么),实现方各自发挥(怎么做)。

三、为什么多态是三大特性的集大成

没有多态的代码长什么样?if isinstance(x, Dog): ... elif isinstance(x, Cat): ...——每加一个类型就要翻遍所有 if 链。多态把【类型判断】从调用方赶走了。

四、动手 Demo:鸭子类型与抽象接口两面夹击

from abc import ABC, abstractmethod

class Shape(ABC):                      # 显式接口:契约
    @abstractmethod
    def area(self): ...

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

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

class Triangle:                        # 不继承 Shape —— 鸭子类型照样入队
    def __init__(self, base, height):
        self.base, self.height = base, height
    def area(self):                    # 有 area 就算数
        return self.base * self.height / 2

shapes = [Rect(3, 4), Circle(2), Triangle(6, 5)]
print([f"{s.area():.2f}" for s in shapes])
print("总面积:", f"{sum(s.area() for s in shapes):.2f}")

try:
    Shape()                            # 抽象类不许实例化
except TypeError as e:
    print("拦截:", str(e)[:38], "...")

class Bad(Shape):
    pass
try:
    Bad()                              # 签了契约不实现也拦
except TypeError as e:
    print("拦截:", str(e)[:38], "...")

沙箱实跑输出:

['12.00', '12.57', '15.00']
总面积: 39.57
拦截: Can't instantiate abstract class Shape ...
拦截: Can't instantiate abstract class Bad with ...

看到什么算成功:三种图形各自算面积、总面积一次求和;抽象类与【不实现 area 的子类】都被拦。

读结果:求总面积的循环不关心图形是什么——只关心有没有 area()。Triangle 连 Shape 都没继承也能混进来(鸭子类型);而 Bad 签了契约不履约,abc 在实例化时拦下。Python 两条路都通:松的靠自觉,紧的靠 abc。

五、30 秒实验

给 Triangle 改名方法为 get_area(),重跑总面积——TypeError:float 对象不可调用(None 没有 area)。鸭子类型的契约虽然不成文,违背了一样当场爆炸——只是爆炸时机从【定义时】推到了【运行时】。

六、常见误区

误区 真相
多态=方法重载 重载(同名不同参,编译期分);多态(同名同参,运行期按对象分)是两回事
Python 没有接口 有鸭子类型这个事实接口;要硬契约用 abc 模块
多态让代码更绕 反了:它把类型 if 链全部赶走;新增类型零改动才是简化
自测三问(点击展开) 1. **多态给调用方带来什么?** 答:只依赖【有某方法】这一事实,不用知道具体类型;新增类型不改旧代码。 2. **鸭子类型是什么?** 答:不看类型血统,只看有没有相应方法——行为即类型。 3. **接口的意义?** 答:把能做什么(契约)与怎么做(实现)分离,双方各自演化。

相关笔记