Python-Mixin 混合类
背景
在谈及混合类之前,首先需要了解 多重继承 的概念,多重继承是指一个类可以继承多个父类,这样就可以从多个父类中继承属性和方法,但是这种设计模式也存在一定的问题。
例如,轿车是一个交通工具,所以轿车类应该继承交通工具这个父类。那民航飞机呢?它也属于交通工具的一种,所以也应该继承交通工具这个父类,但是交通工具这个类应该怎么设计?是否应该实现飞行功能?如果实现,那轿车继承交通工具父类显然不合适,因为轿车根本没有飞行功能。如果不实现,民航飞机继承交通工具类也同样不合适。如果两者都分别实现自己的方法,那将会违背代码重用的原则,那应该这么解决这个问题?事实上,我们可以把地上跑的,天上飞的,甚至水上漂的这些工具的功能抽象出来实现交通工具这个父类。对于飞机来说,那就去继承交通工具和有飞行功能两个父类,对于船来说,那就去继承交通工具和有水上漂功能的两个父类。但是这样子的多重继承说到底还是违背了“is-a”的原则,这个问题应该怎么样处理?
- is-a 是一种继承关系,A 是 B 的一种,比如在长方体和正方体之间 -- 正方体是长方体的一种
- has-a 是一种组合关系,A 有 B,比如在人和手之间 -- 人有手,汽车有轮胎
再举一个实际工作的问题,如果我们需要实现一个日志器,我们同时需要他输出到屏幕和输出到文件中的两个功能,但是我们想做到自由组合,比如在开发环境中,我们只需要输出屏幕,在生产环境中,我们只需要输出到文件中,在测试环境中,我全都要,那该如何实现?
用混合类可以比较巧妙的解决这个问题,混合类是一种特殊的类,它不是一个真正的类,它只是一个类的集合,它的作用是把一些功能抽象出来,然后让其他类去继承它,这样就可以实现代码的重用。
概念
Mixin
故名思义 Mix in
,也被称为混合类,不只是 python 中有,通常作为一种编程范式,在面对对象的语言中常见。Mixin
的作用是为一个类提供额外的功能,而不需要继承这个类。Mixin
的特点是可以被多个类继承,且不会影响到其他类的继承关系。
举例
拿 [背景](# 背景) 中的例子定义一个简单的类:
class Displayer():
def display(self, message):
print(message)
class LoggerMixin():
def log(self, message, filename='logfile.txt'):
with open(filename, 'a') as fh:
fh.write(message)
def display(self, message):
super().display(message)
self.log(message)
class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message, filename='subclasslog.txt')
subclass = MySubClass()
subclass.display("This string will be shown and logged in subclasslog.txt")
在上述例子中,我们定义了三个类,其中 Displayer
类只有一个 display
方法,LoggerMixin
类有两个方法,一个是 log
方法,一个是 display
方法,MySubClass
类继承了 LoggerMixin
和 Displayer
类,MySubClass
类重写了 LoggerMixin
类中的 log
方法,这样就实现了在 MySubClass
类中,display
方法既会显示到屏幕,又会写入到文件中。
逻辑很简单,但是仔细看,在第 6 行中,我们定义的 LoggerMixin
实际上没有继承任何父类,那何来的 super() 方法呢?
super 方法是调用父类的方法。
在多继承的环境下,super()
方法有比较复杂的含义,它会按照 MRO(Method Resolution Order)继承链的顺序来查找父类,MRO 的顺序是在类定义时就确定的,不会随着子类的定义而改变。
触发过程
那么,在调用 MySubClass.display()
方法时,触发了以下行为
- 首先调用了
LoggerMixin.display
方法,因为在继承关系上,它最近 - 其次
LoggerMixin.display
调用super().display()
,上文说到,super()
会寻找当前类的 MRO,那么当前类是MysubClass
,在继承链上,过了LoggerMixin
就是Displayer
,所以去找Displayer
的display
方法。 - 当然也要调用
LoggerMixin
的self.log
方法,那么这个self
又是谁?,实际上也是 MySubClass 的实例,所以调用的是MySubClass.log()
,那为什么又可以输出到文件呢?因为MySubClass.log()
调用了super().log()
,MRO 来寻找最近的父类,那就是LoggerMixin.log()
咯!
讲清楚这一系列触发过程,基本就能理解 Mix-in 这个概念了
简单理解(省流)
在调用实例方法中,我们会现在当前类中找,没有的话,就去 MRO 中找,顺序是从左到右 ➡️。
所以 Mixin 类专注于多继承函数的调用问题,总是需要和其他类混合来加强功能!
用途
两个字:优雅!可以大大简化代码开发过程,减少耦合。
实际中的应用
在 Django 和 DRF 框架中,应用较多,比如有两个接口,一个接口 A 需要返回表格展示所需数据,一个接口 B 需要返回这些数据的文件下载流,在业务逻辑上,两个接口是一样的,那么就可以用混合类的方法,接口 B 继承一个混合类 C 和 A,其中混合类重写 get
方法,来返回文件流,并继承 A 的数据获取。
在 VSCode 中,如果混合类的类名后缀没有包含 Mixin,编辑器会给波浪线,但似乎并不影响代码运行。
Here's a simple footnote,1 and here's a longer one.bignote
Table Header 1 | Table Header 2 | Table Header 3 |
---|---|---|
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |
Align Left | Align Center | Align Right |
---|---|---|
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |