Files

7.7 KiB
Raw Permalink Blame History

第 3-3 课Python 与 Java 的继承和多态差异

一、本课定位

你已经掌握 Java 的继承、接口和多态,因此本课不重复通用理论,重点建立以下 Python 差异:

  • super() 不需要显式写父类名;
  • Python 没有强制的 @Override
  • Python 支持多继承,并使用方法解析顺序决定查找路径;
  • 多态调用经常基于鸭子类型,不要求对象继承共同父类或显式实现接口。

二、本课目标

完成本课后,你将能够:

  1. 定义父类和子类并重写方法;
  2. 使用 super() 复用继承链中的实现;
  3. 解释方法解析顺序Method Resolution OrderMRO
  4. 使用鸭子类型Duck Typing完成统一调用
  5. 理解 Python 多继承和 Java 单类继承的差异;
  6. 避免通过大量 isinstance() 分支模拟多态。

三、差异速查表

关注点 Java Python
继承类 extends class Child(Parent):
调用继承逻辑 super(...) 常用 super()
重写标记 推荐 @Override 没有强制对应标记
类继承数量 单继承 支持多继承
方法查找 类继承链 按 MRO 查找
接口多态 通常显式 implements 常见鸭子类型,无需声明接口
类型判断 编译期和运行时类型体系 运行时更关注对象是否提供所需行为

四、继承和方法重写

class Notifier:
    def send(self, message: str) -> None:
        raise NotImplementedError("子类必须实现 send() 方法。")


class EmailNotifier(Notifier):
    def send(self, message: str) -> None:
        print(f"发送邮件:{message}")

Python 子类把父类写在类名后的圆括号中,不使用 extends。子类定义同名方法即可重写,语言本身不要求添加 @Override

缺少 @Override 也意味着方法名拼错时不会获得同样的编译期保护,因此测试和静态检查更加重要。

五、正确理解 super()

class EmailNotifier(Notifier):
    def __init__(self, sender: str, address: str) -> None:
        super().__init__(sender)
        self.address = address

Python 3 通常直接写 super(),不需要传入当前类和 self

在简单单继承中,可以暂时把它理解为调用父类实现。但更准确的理解是:super() 会沿当前类的方法解析顺序继续查找方法。这个区别在多继承中尤其重要。

不要写死父类名称:

Notifier.__init__(self, sender)

写死类名可能破坏多继承中的协作调用链。

六、方法解析顺序 MRO

Python 支持一个类继承多个父类:

class LoggedEmailNotifier(EmailNotifier, LogMixin):
    pass

可以查看查找顺序:

print(LoggedEmailNotifier.mro())

当调用某个方法时Python 会按 MRO 中的顺序寻找第一个匹配实现。日常代码应避免设计过于复杂的多继承层次。

常见的安全用途是混入类Mixin它只提供一项小而明确的可复用能力通常不独立代表完整业务对象。

七、鸭子类型

经典描述是:“如果一个对象走起来像鸭子、叫起来像鸭子,就把它当作鸭子使用。”

在代码中,这表示调用者更关心对象是否提供所需行为:

def send_message(notifier, message: str) -> None:
    notifier.send(message)

下面两个类不必拥有共同父类:

class EmailNotifier:
    def send(self, message: str) -> None:
        ...


class ConsoleNotifier:
    def send(self, message: str) -> None:
        ...

只要对象具有可调用的 send() 方法,send_message() 就可以使用它。

与 Java 接口多态相比:

  • Java 常在编译期要求对象声明实现某个接口;
  • Python 运行时可以直接尝试调用所需方法;
  • Python 也能使用协议Protocol为鸭子类型提供静态检查后续结合类型设计再展开。

八、不要用类型分支代替多态

不推荐:

if isinstance(processor, CardProcessor):
    ...
elif isinstance(processor, QrCodeProcessor):
    ...

推荐让每个对象自己实现统一行为:

processor.pay(amount)

这样新增处理器时,调用方通常不需要修改。

isinstance() 本身并非错误;当业务规则确实依赖类型时可以使用。但如果只是为了选择同一操作的不同实现,通常应优先使用多态。

九、继承还是组合

Java 与 Python 都不应为了复用少量代码而滥用继承。

  • 真正存在“是一个”关系,并需要替换父类型行为时,可以考虑继承;
  • 只是“拥有一个”协作者或需要灵活替换能力时,优先考虑组合;
  • 只需要调用统一行为时Python 的鸭子类型可能已经足够。

组合会在后续业务模型课程中结合实例继续练习。

十、完整示例和运行方法

示例文件:

03_面向对象/3_3_Python与Java的继承和多态差异/inheritance_polymorphism_comparison.py

在项目根目录运行:

python .\03_面向对象\3_3_Python与Java的继承和多态差异\inheritance_polymorphism_comparison.py

示例输出会直接展示:

  1. super() 复用继承逻辑;
  2. 方法重写;
  3. 没有共同父类的鸭子类型调用;
  4. 多继承类的 MRO。

十一、常见错误

11.1 把 super() 当成固定父类对象

它实际按照 MRO 继续查找。在多继承中,下一站不一定是你凭直觉认定的某个父类。

11.2 忘记调用父类初始化逻辑

如果父类负责建立必要属性,子类通常需要调用 super().__init__(...),否则使用父类属性时可能出现 AttributeError

11.3 认为多态必须继承共同父类

Python 鸭子类型只要求对象提供当前操作需要的方法。

11.4 滥用多继承

多继承会增加 MRO 和初始化协作的理解成本。优先使用清晰的小型 Mixin复杂业务能力通常更适合组合。

11.5 假设方法已经被正确重写

Python 没有强制 @Override。方法名或参数写错时,应通过测试、类型检查或代码审查发现。

十二、课堂练习

打开:

03_面向对象/3_3_Python与Java的继承和多态差异/practice.py

完成支付处理器练习:

  1. 定义父类统一操作;
  2. 子类通过 super() 复用初始化;
  3. 未继承父类的二维码处理器参与统一调用;
  4. 使用一个函数调用两种处理器;
  5. 输出并观察 CardProcessor.mro()

十三、参考答案与验证标准

参考答案暂不写入练习文件。完成后按以下层级验证:

  • 必须修复:语法错误、运行错误、支付结果错误、没有使用 super()、通过具体类型分支实现调用;
  • 建议改进:重复代码、职责混乱、明显影响理解的命名;
  • 可选优化:非必要类型注解、输出美化和不影响行为的格式问题。

只有“必须修复”会阻止课程验收。

十四、本课小结

  • Python 使用 class Child(Parent) 表示继承;
  • 子类定义同名方法即可重写,没有强制 @Override
  • super() 会沿 MRO 继续查找实现;
  • Python 支持多继承,但复杂多继承应谨慎使用;
  • 鸭子类型关注对象提供的行为,不要求共同父类或显式接口;
  • 统一调用不应依赖大量具体类型判断;
  • 继承用于真正的类型关系,单纯复用通常优先考虑组合。

十五、验收标准

  • 能正确使用 super().__init__()
  • 能重写父类方法;
  • 能输出并解释简单类的 MRO
  • 能让未继承共同父类的对象参与统一调用;
  • 能解释鸭子类型与 Java 接口多态的区别;
  • 能避免通过 isinstance() 分支实现本题多态;
  • practice.py 输出正确支付结果。