多重继承能发挥积极作用。《设计模式:可复用面向对象软件的基础》一书中的适配器模式用的就是多重继承,因此使用多重继承肯定没有错(那本书中的其他 22 个设计模式都使用单继承,因此多重继承显然不是灵丹妙药)。

在 Python 标准库中,最常使用多重继承的是 collections.abc 包。这没什么问题,毕竟连 Java 都支持接口的多重继承,而抽象基类就是接口声明,只不过它可以提供具体方法的实现。5

5前面说过,Java 8 也允许提供方法实现。这个新功能在官方的 Java 教程中叫默认方法(https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html)。

在标准库中,GUI 工具包 Tkinter(tkinter 模块是 Tcl/Tk 的 Python 接口,https://docs.python.org/3/library/tkinter.html)把多重继承用到了极致。图 12-2 中展示的方法解析顺序是 Tkinter 小组件层次结构的一部分,图 12-3 则列出了 tkinter 基包中的全部小组件类(tkinter.ttk 子包中还有一些,https://docs.python.org/3/library/tkinter.ttk.html)。

{%}

图 12-3:Tkinter GUI 类层次结构的 UML 简图;使用 «mixin» 标记的类通过多重继承为其他类提供具体方法

写作本书时,Tkinter 已经 20 岁了,不能代表当下的最佳实践。但是,它却能表明当没有意识到多重继承的缺点时,程序员是如何使用多重继承的。下一节讨论一些好的做法时,会把 Tkinter 作为反面教材。

来看图 12-3 中的几个类。

Toplevel:表示 Tkinter 应用程序中顶层窗口的类。

Widget:窗口中所有可见对象的超类。

Button:普通的按钮小组件。

Entry:单行可编辑文本字段。

Text:多行可编辑文本字段。

这几个类的方法解析顺序如下,这些输出使用示例 12-8 中定义的 print_mro 函数得到:

>>> import tkinter
>>> print_mro(tkinter.Toplevel)
Toplevel, BaseWidget, Misc, Wm, object
>>> print_mro(tkinter.Widget)
Widget, BaseWidget, Misc, Pack, Place, Grid, object
>>> print_mro(tkinter.Button)
Button, Widget, BaseWidget, Misc, Pack, Place, Grid, object
>>> print_mro(tkinter.Entry)
Entry, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, object
>>> print_mro(tkinter.Text)
Text, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, YView, object

在类之间的关系方面有几点要注意。

下面将讨论多重继承一些好的做法,看看 Tkinter 有没有践行。