Python是面向对象的编程语言,它提供了一些内置的编程机制,使得开发者可以适当地实现多重继承。但是我们仍然应该尽量避开多重继承。
前言
若一定要利用多重继承所带来的便利及封装性,那就编写mix-in
类。mix-in
是一种小型的类,它只定义了其他类可能需要提供的一套附加方法,而不定义自己的实例属性,此外,它也不要求使用者调用自己的__init__
构造器。
例子1:ToDictMixin
现在,要把内存中的Python对象转换成字典形式,以便将其序列化,那我们就不妨把这个功能写成通用的代码,以便其他类使用。
ToDictMixin
1 | # ToDictMixin类 |
BinaryTree
使用ToDictMixin
把二叉树表示为字典:
1 | # 二叉树类 |
现在,我们可以把一大批互相关联的Python对象都轻松地转换成字典:
1 | tree = BinaryTree(1, |
BinaryTreeWithParent
mix-in
的最大优势在于,使用者可以随时安插这些通用的功能,并能在必要的时候覆写它们。
下面定义的这个BinaryTree
子类,会持有指向父节点的引用。如果采用默认的ToDictMixin.to_dict
来处理它,那么程序就会因为循环引用而陷入死循环(parent)。
1 | class BinaryTreeWithParent(BinaryTree): |
解决办法是在BinaryTreeWithParent
里覆写ToDictMixin._traverse方法
,令该方法只处理与序列化有关的值,从而使mix-in的实现代码不会陷入死循环:
1 | # 覆写_traverse方法,不再遍历父节点,而是只把父节点所对应的数值插入到最终生成的字典里面 |
调用BinaryTreeWithParent.to_dict
看看:
1 | root = BinaryTreeWithParent(1) |
定义了BinaryTreeWithParent._traverse
方法之后,如果其他类的某个属性也是BinaryTreeWithParent
类型,那么ToDictMixin
会自动处理好这些属性:
1 | class NamedSubTree(ToDictMixin): |
多个mix-in组合
JsonMixin
多个mix-in之间也可以相互组合。例如,可以编写这样一个mix-in,它能够为任意类提供通用的JSON序列化功能。我们可以假定:继承了mix-in的哪个类,会提供名为to_dict的方法(此方法有可能是那个类通过多重继承ToDictMixin而具备的,也有可能不是)。
1 | # import json first |
请注意,JsonMixin类既定义了实例方法,有定义了类方法。这两种行为都可以通过mix-in来提供。在本例中,凡是想继承JsonMixin的类,只需符合两个条件即可:
- (1) 包含名为to_dict的方法
- (2) init方法接受关键字参数
组合ToDictMixin和JsonMixin
我们用下面这个继承了mix-in组件的数据类来表示数据中心的拓扑结构:
1 | class DatacenterRack(ToDictMixin, JsonMixin): |
对这样的类进行序列化,以及从JSON中加载它,都是比较简单的。下面的这段代码,会重复执行序列化及反序列化操作,以验证这两个功能有没有正确地实现出来。
1 | serialized = """{ |
使用这种mix-in的时候,既可以像本例这样,直接继承多个mix-in组件,也可以先令继承体系中的其他类继承相关的mix-in组件,然后再令本类继承那些类,以达到同样的效果
小结
- 能用mix-in组件实现的效果,就不要用多重继承来做
- 将各功能实现为可插拔的mix-in组件,然后令相关的类继承自己需要的那些组件,即可定制该类实例所应具备的行为。
- 把简单的行为封装到mix-in组件里,然后就可以用多个mix-in组合出复杂的行为了。