feat(面向对象): 完成第三阶段差异化课程

This commit is contained in:
zhiye.sun
2026-08-11 17:53:53 +08:00
parent b348dc0a1f
commit f1d9548646
16 changed files with 2131 additions and 14 deletions

View File

@@ -0,0 +1,242 @@
# 第 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` | 常见鸭子类型,无需声明接口 |
| 类型判断 | 编译期和运行时类型体系 | 运行时更关注对象是否提供所需行为 |
## 四、继承和方法重写
```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` 输出正确支付结果。