Python: Mixin Pattern¶
Summary¶
在面向对象 OOP 中 Mixin (混合) 是一种设计模式. 跟继承不同的是, Mixin 类并不是父类的延伸, 而是对父类的功能进行扩展的可插拔功能. 这里的重点是可插拔. 跟继承不一样, 继承了以后子类就是子类, 父类就是父类, 用户两个类都会用到. 而 Mixin 模式中通常只有父类会被用到, 而父类本质上是 父类 + Mixin 类 最终形成的一个类. Mixin 类不会被直接用到.
这个模式在什么时候有用呢? 当你要写一个很复杂的类, 里面的 method (方法) 超级多的时候, 如果你把所有的方法放在一个模块中, 那么这是相当不好测试和维护的. 用继承的模式的话, 相当于你把它分拆成 N 个 .py 文件, 按照顺序, 第 1 个是 class1, 然后第 2 个继承第一个是 class2, 第 3 个又继承第二个是 class3. 这种顺序关系从逻辑上其实是不成立的. 诚然里面的 method 是有一些依赖关系的, 但是大部分其实是相互独立的关系, 将他们做成可插拔的 mixin 会更好. 这个时候就是 Mixin 模式的应用场景了.
由于 Python 中有 TypeHint, 你希望在每个 mixin 类都能利用上所有其他 mixin 类的 type hint, 这需要一点点设计. 请看下面的例子.
Example¶
模块的结构如下:
my_lib
|--- __init__.py
|--- base.py
|--- mixin1.py
|--- mixin2.py
|--- my_class.py
我们有一个基类在 base.py 中. 这里主要定义了 constructor, 而不管其他的. 而 mixin1, 2, … 都是 Mixin 类. 而最后给用户的 API 是 my_class.py 中的 MyClass. 在 TypeHint 中我们都利用了 TYPE_CHECKING 这个办法只提供 type hint, 不真正的 import. 从而让所有的 Mixin 类都认为自己是最后的 MyClass, 从而获得了其他 Mixin 类中的 type hint.
import typing as T
if T.TYPE_CHECKING:
from .my_class import MyClass
# -*- coding: utf-8 -*-
# content of: base.py
class MyClass:
def __init__(self):
self.base = 0
# -*- coding: utf-8 -*-
# content of: mixin1.py
import typing as T
if T.TYPE_CHECKING:
from .my_class import MyClass
class Mixin1:
@property
def a(self: 'MyClass') -> int:
return self.base + 1
# -*- coding: utf-8 -*-
# content of: mixin2.py
import typing as T
if T.TYPE_CHECKING:
from .my_class import MyClass
class Mixin2:
@property
def b(self: 'MyClass') -> int:
return self.base + 2
# -*- coding: utf-8 -*-
# content of: my_class.py
from .base import MyClass as Base
from .mixin1 import Mixin1
from .mixin2 import Mixin2
class MyClass(
Base,
Mixin1,
Mixin2,
):
pass
最后你可以从最终用户的脚步在这个 test.py 进行测试. 你发现你的 type hint 能检测到所有的 Mixin 中定义的东西.
# -*- coding: utf-8 -*-
from my_lib import MyClass
my_class = MyClass()
print(f"my_class.base = {my_class.base}")
print(f"my_class.a = {my_class.a}")
print(f"my_class.b = {my_class.b}")