234 lines
7.0 KiB
Markdown
234 lines
7.0 KiB
Markdown
---
|
||
sources: [summaries/01_Dicts_revisited.md]
|
||
brief: Mixin 模式通过多重继承把可复用行为片段混入不同类中。
|
||
---
|
||
|
||
# Mixin 模式
|
||
|
||
Mixin 模式是一种利用多重继承进行代码复用的设计方式:把一小段可复用行为封装到一个类中,再将这个类“混入”到其他类的继承列表里,使原本不相关的类获得相同能力。
|
||
|
||
在 Python 中,Mixin 常与 继承与MRO、属性查找 和 `super()` 配合使用。相关来源见 [[summaries/01_Dicts_revisited]]。
|
||
|
||
## 核心思想
|
||
|
||
Mixin 类通常不是完整的业务对象,而是一个“行为片段”。它的职责很小,通常只提供某个特定能力,例如:
|
||
|
||
- 让对象输出更大声的声音。
|
||
- 增加日志记录能力。
|
||
- 增加序列化能力。
|
||
- 增加比较、验证、缓存等横切行为。
|
||
|
||
它本身往往不能独立实例化使用,而是要和其他类一起组成最终类。
|
||
|
||
## 来源文档中的示例
|
||
|
||
[[summaries/01_Dicts_revisited]] 中使用 `Dog` 和 `Bike` 展示了 Mixin 的动机。
|
||
|
||
原本有两个互不相关的类:
|
||
|
||
```python
|
||
class Dog:
|
||
def noise(self):
|
||
return 'Bark'
|
||
|
||
def chase(self):
|
||
return 'Chasing!'
|
||
```
|
||
|
||
```python
|
||
class Bike:
|
||
def noise(self):
|
||
return 'On Your Left'
|
||
|
||
def pedal(self):
|
||
return 'Pedaling!'
|
||
```
|
||
|
||
如果要分别实现“更大声”的版本,可能会写出重复代码:
|
||
|
||
```python
|
||
class LoudDog(Dog):
|
||
def noise(self):
|
||
return super().noise().upper()
|
||
```
|
||
|
||
```python
|
||
class LoudBike(Bike):
|
||
def noise(self):
|
||
return super().noise().upper()
|
||
```
|
||
|
||
这两个 `noise()` 方法的实现完全相同,都是调用下一个类的 `noise()`,再转为大写。
|
||
|
||
Mixin 模式把这段共同行为抽出来:
|
||
|
||
```python
|
||
class Loud:
|
||
def noise(self):
|
||
return super().noise().upper()
|
||
```
|
||
|
||
然后通过多重继承组合:
|
||
|
||
```python
|
||
class LoudDog(Loud, Dog):
|
||
pass
|
||
|
||
class LoudBike(Loud, Bike):
|
||
pass
|
||
```
|
||
|
||
这样,`LoudDog` 和 `LoudBike` 都获得了“大声化”的 `noise()` 行为,而该逻辑只实现了一次。
|
||
|
||
## Mixin 为什么依赖 MRO
|
||
|
||
Mixin 的行为能正确工作,依赖 Python 的方法解析顺序,即 MRO。
|
||
|
||
例如:
|
||
|
||
```python
|
||
class LoudDog(Loud, Dog):
|
||
pass
|
||
```
|
||
|
||
当调用:
|
||
|
||
```python
|
||
LoudDog().noise()
|
||
```
|
||
|
||
Python 会按照 `LoudDog.__mro__` 查找方法。大致顺序是:
|
||
|
||
```python
|
||
LoudDog -> Loud -> Dog -> object
|
||
```
|
||
|
||
因此:
|
||
|
||
1. Python 先在 `LoudDog` 中查找 `noise()`。
|
||
2. 如果没有找到,就查找 `Loud`。
|
||
3. 在 `Loud.noise()` 中调用 `super().noise()`。
|
||
4. `super()` 会继续沿 MRO 查找下一个类,也就是 `Dog`。
|
||
5. `Dog.noise()` 返回 `'Bark'`。
|
||
6. `Loud.noise()` 把结果转成大写,得到 `'BARK'`。
|
||
|
||
这说明 `super()` 在多重继承中不是简单地“调用父类”,而是调用 MRO 中的下一个类。相关机制见 继承与MRO。
|
||
|
||
## 为什么 Mixin 类通常放在前面
|
||
|
||
在示例中,类定义写作:
|
||
|
||
```python
|
||
class LoudDog(Loud, Dog):
|
||
pass
|
||
```
|
||
|
||
而不是:
|
||
|
||
```python
|
||
class LoudDog(Dog, Loud):
|
||
pass
|
||
```
|
||
|
||
原因是 Python 按照 MRO 查找属性和方法。若 `Dog` 放在 `Loud` 前面,调用 `noise()` 时可能先找到 `Dog.noise()`,从而不会进入 `Loud.noise()`,Mixin 的增强逻辑就不会生效。
|
||
|
||
因此,Mixin 通常放在继承列表较前的位置,用来优先拦截或扩展方法调用。
|
||
|
||
## Mixin 与普通父类的区别
|
||
|
||
Mixin 和普通父类都使用继承语法,但它们的设计目的不同。
|
||
|
||
| 类型 | 主要目的 | 是否通常独立使用 | 典型特征 |
|
||
| --- | --- | --- | --- |
|
||
| 普通父类 | 表达“是一种”的层级关系 | 通常可以 | 定义核心身份和主要行为 |
|
||
| Mixin 类 | 提供可组合的行为片段 | 通常不单独使用 | 职责小、可复用、依赖其他类提供基础方法 |
|
||
|
||
例如:
|
||
|
||
```python
|
||
class Dog:
|
||
...
|
||
```
|
||
|
||
`Dog` 表示一种具体对象。
|
||
|
||
```python
|
||
class Loud:
|
||
...
|
||
```
|
||
|
||
`Loud` 不表示一种完整对象,而表示“把声音变大”的能力。
|
||
|
||
## Mixin 与属性查找
|
||
|
||
Mixin 的底层运行仍然建立在 Python 的属性查找机制上。
|
||
|
||
根据 [[summaries/01_Dicts_revisited]],对象方法通常存放在类的 `__dict__` 中。访问方法时,Python 会:
|
||
|
||
1. 先查找实例自己的 `__dict__`。
|
||
2. 再按照类的 `__mro__` 查找各个类的 `__dict__`。
|
||
3. 找到第一个匹配名称后停止。
|
||
|
||
Mixin 之所以能工作,是因为它把方法放入了继承链中的某个类字典里,并借助 MRO 参与属性查找。相关内容见 属性查找 和 Python对象模型。
|
||
|
||
## `super()` 在 Mixin 中的重要性
|
||
|
||
Mixin 中应优先使用 `super()`,而不是硬编码某个父类名。
|
||
|
||
推荐写法:
|
||
|
||
```python
|
||
class Loud:
|
||
def noise(self):
|
||
return super().noise().upper()
|
||
```
|
||
|
||
不推荐写法:
|
||
|
||
```python
|
||
class Loud:
|
||
def noise(self):
|
||
return Dog.noise(self).upper()
|
||
```
|
||
|
||
原因是 Mixin 的目标是与不同类组合。`Loud` 既可能混入 `Dog`,也可能混入 `Bike`,甚至混入其他提供 `noise()` 方法的类。如果硬编码 `Dog.noise()`,它就不再是通用的 Mixin。
|
||
|
||
使用 `super()` 后,`Loud` 不需要知道下一个类是谁,只需要相信 MRO 会把调用传递给合适的下一个实现。
|
||
|
||
## Mixin 的优点
|
||
|
||
Mixin 模式的主要优点包括:
|
||
|
||
- **减少重复代码**:相同行为只实现一次。
|
||
- **增强组合能力**:可以把多个小能力组合到一个类中。
|
||
- **避免深层继承树**:不必为了每种组合都创建一条复杂继承链。
|
||
- **适合横切功能**:日志、验证、格式化、序列化等能力可以作为 Mixin。
|
||
- **支持无关类复用**:即使类之间没有自然继承关系,也能共享行为。
|
||
|
||
## Mixin 的风险
|
||
|
||
Mixin 依赖多重继承,因此也继承了多重继承的复杂性。
|
||
|
||
常见风险包括:
|
||
|
||
- MRO 顺序不直观,导致方法调用结果难以预测。
|
||
- 多个 Mixin 定义同名方法时可能发生冲突。
|
||
- Mixin 隐式依赖其他类提供某些方法,例如 `Loud` 依赖后续类提供 `noise()`。
|
||
- 过度使用会让类的行为来源分散,降低可读性。
|
||
|
||
因此,Mixin 适合小而明确的行为,不适合承载复杂业务核心逻辑。
|
||
|
||
## 使用建议
|
||
|
||
设计 Mixin 时可遵循以下原则:
|
||
|
||
1. **职责单一**:一个 Mixin 只提供一种清晰能力。
|
||
2. **命名明确**:通常使用 `SomethingMixin` 或表达能力的名称,例如 `Loud`。
|
||
3. **避免保存复杂状态**:Mixin 最好少引入实例变量,减少与其他类冲突。
|
||
4. **使用 `super()`**:保证能参与协作式多重继承。
|
||
5. **文档说明依赖**:如果 Mixin 要求宿主类提供某个方法,应明确说明。
|
||
6. **控制继承顺序**:将 Mixin 放在合适位置,使其能按预期参与 MRO。
|
||
|
||
## 简要总结
|
||
|
||
Mixin 模式是 Python 多重继承的典型用途之一。它通过小型类封装可复用行为,再借助 MRO 和 `super()` 将行为组合进不同类中。理解 Mixin,需要同时理解 Python 的类字典、属性查找、绑定方法、多重继承和 MRO 机制。 |