feat(面向对象): 完成第三阶段差异化课程
This commit is contained in:
242
03_面向对象/3_3_Python与Java的继承和多态差异/README.md
Normal file
242
03_面向对象/3_3_Python与Java的继承和多态差异/README.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 第 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` 输出正确支付结果。
|
||||
Reference in New Issue
Block a user