# 第 3-3 课:Python 与 Java 的继承和多态差异 ## 一、本课定位 你已经掌握 Java 的继承、接口和多态,因此本课不重复通用理论,重点建立以下 Python 差异: - `super()` 不需要显式写父类名; - Python 没有强制的 `@Override`; - Python 支持多继承,并使用方法解析顺序决定查找路径; - 多态调用经常基于鸭子类型,不要求对象继承共同父类或显式实现接口。 ## 二、本课目标 完成本课后,你将能够: 1. 定义父类和子类并重写方法; 2. 使用 `super()` 复用继承链中的实现; 3. 解释方法解析顺序(Method Resolution Order,MRO); 4. 使用鸭子类型(Duck Typing)完成统一调用; 5. 理解 Python 多继承和 Java 单类继承的差异; 6. 避免通过大量 `isinstance()` 分支模拟多态。 ## 三、差异速查表 | 关注点 | Java | Python | |---|---|---| | 继承类 | `extends` | `class Child(Parent):` | | 调用继承逻辑 | `super(...)` | 常用 `super()` | | 重写标记 | 推荐 `@Override` | 没有强制对应标记 | | 类继承数量 | 单继承 | 支持多继承 | | 方法查找 | 类继承链 | 按 MRO 查找 | | 接口多态 | 通常显式 `implements` | 常见鸭子类型,无需声明接口 | | 类型判断 | 编译期和运行时类型体系 | 运行时更关注对象是否提供所需行为 | ## 四、继承和方法重写 ```python 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() ```python class EmailNotifier(Notifier): def __init__(self, sender: str, address: str) -> None: super().__init__(sender) self.address = address ``` Python 3 通常直接写 `super()`,不需要传入当前类和 `self`。 在简单单继承中,可以暂时把它理解为调用父类实现。但更准确的理解是:`super()` 会沿当前类的方法解析顺序继续查找方法。这个区别在多继承中尤其重要。 不要写死父类名称: ```python Notifier.__init__(self, sender) ``` 写死类名可能破坏多继承中的协作调用链。 ## 六、方法解析顺序 MRO Python 支持一个类继承多个父类: ```python class LoggedEmailNotifier(EmailNotifier, LogMixin): pass ``` 可以查看查找顺序: ```python print(LoggedEmailNotifier.mro()) ``` 当调用某个方法时,Python 会按 MRO 中的顺序寻找第一个匹配实现。日常代码应避免设计过于复杂的多继承层次。 常见的安全用途是混入类(Mixin):它只提供一项小而明确的可复用能力,通常不独立代表完整业务对象。 ## 七、鸭子类型 经典描述是:“如果一个对象走起来像鸭子、叫起来像鸭子,就把它当作鸭子使用。” 在代码中,这表示调用者更关心对象是否提供所需行为: ```python def send_message(notifier, message: str) -> None: notifier.send(message) ``` 下面两个类不必拥有共同父类: ```python class EmailNotifier: def send(self, message: str) -> None: ... class ConsoleNotifier: def send(self, message: str) -> None: ... ``` 只要对象具有可调用的 `send()` 方法,`send_message()` 就可以使用它。 与 Java 接口多态相比: - Java 常在编译期要求对象声明实现某个接口; - Python 运行时可以直接尝试调用所需方法; - Python 也能使用协议(Protocol)为鸭子类型提供静态检查,后续结合类型设计再展开。 ## 八、不要用类型分支代替多态 不推荐: ```python if isinstance(processor, CardProcessor): ... elif isinstance(processor, QrCodeProcessor): ... ``` 推荐让每个对象自己实现统一行为: ```python processor.pay(amount) ``` 这样新增处理器时,调用方通常不需要修改。 `isinstance()` 本身并非错误;当业务规则确实依赖类型时可以使用。但如果只是为了选择同一操作的不同实现,通常应优先使用多态。 ## 九、继承还是组合 Java 与 Python 都不应为了复用少量代码而滥用继承。 - 真正存在“是一个”关系,并需要替换父类型行为时,可以考虑继承; - 只是“拥有一个”协作者或需要灵活替换能力时,优先考虑组合; - 只需要调用统一行为时,Python 的鸭子类型可能已经足够。 组合会在后续业务模型课程中结合实例继续练习。 ## 十、完整示例和运行方法 示例文件: ```text 03_面向对象/3_3_Python与Java的继承和多态差异/inheritance_polymorphism_comparison.py ``` 在项目根目录运行: ```powershell 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`。方法名或参数写错时,应通过测试、类型检查或代码审查发现。 ## 十二、课堂练习 打开: ```text 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` 输出正确支付结果。