当前位置: 首页 > news >正文

Python 继承的优缺点(处理多重继承)

处理多重继承

如 Alan Kay 所言,继承有很多用途,而多重继承增加了可选方案和复
杂度。使用多重继承容易得出令人费解和脆弱的设计。我们还没有完整
的理论,下面是避免把类图搅乱的一些建议。

把接口继承和实现继承区分开

使用多重继承时,一定要明确一开始为什么创建子类。主要原因可
能有:

  • 继承接口,创建子类型,实现“是什么”关系
  • 继承实现,通过重用避免代码重复

其实这两条经常同时出现,不过只要可能,一定要明确意图。通过
继承重用代码是实现细节,通常可以换用组合和委托模式。而接口
继承则是框架的支柱。

使用抽象基类显式表示接口

现代的 Python 中,如果类的作用是定义接口,应该明确把它定义为
抽象基类。Python 3.4 及以上的版本中,我们要创建 abc.ABC 或其
他抽象基类的子类(如果想支持较旧的 Python 版本,参见 11.7.1
节)。

通过混入重用代码

如果一个类的作用是为多个不相关的子类提供方法实现,从而实现
重用,但不体现“是什么”关系,应该把那个类明确地定义为混入类
(mixin class)。从概念上讲,混入不定义新类型,只是打包方
法,便于重用。混入类绝对不能实例化,而且具体类不能只继承混
入类。混入类应该提供某方面的特定行为,只实现少量关系非常紧
密的方法。

在名称中明确指明混入

因为在 Python 中没有把类声明为混入的正规方式,所以强烈推荐在
名称中加入 …Mixin 后缀。Tkinter 没有采纳这个建议,如果采纳
的话,XView 会变成 XViewMixin,Pack 会变成 PackMixin,图
12-3 中所有使用 «mixin» 标记的类都应该这么做。

抽象基类可以作为混入,反过来则不成立

抽象基类可以实现具体方法,因此也可以作为混入使用。不过,抽
象基类会定义类型,而混入做不到。此外,抽象基类可以作为其他
类的唯一基类,而混入决不能作为唯一的超类,除非继承另一个更
具体的混入——真实的代码很少这样做。

抽象基类有个局限是混入没有的:抽象基类中实现的具体方法只能
与抽象基类及其超类中的方法协作。这表明,抽象基类中的具体方
法只是一种便利措施,因为这些方法所做的一切,用户调用抽象基
类中的其他方法也能做到。

不要子类化多个具体类

具体类可以没有,或最多只有一个具体超类。 也就是说,具体类
的超类中除了这一个具体超类之外,其余的都是抽象基类或混入。
例如,在下述代码中,如果 Alpha 是具体类,那么 Beta 和 Gamma
必须是抽象基类或混入:

class MyConcreteClass(Alpha, Beta, Gamma):
"""这是一个具体类,可以实例化。"""
# ……更多代码……

为用户提供聚合类

如果抽象基类或混入的组合对客户代码非常有用,那就提供一个
类,使用易于理解的方式把它们结合起来。Grady Booch 把这种类
称为聚合类(aggregate class)。

例如,下面是 tkinter.Widget 类的完整代码
(https://hg.python.org/cpython/file/3.4/Lib/tkinter/init.py#l2141):

class Widget(BaseWidget, Pack, Place, Grid):
"""Internal class.
Base class for a widget which can be positioned with the
geometry managers Pack, Place or Grid."""
pass

Widget 类的定义体是空的,但是这个类提供了有用的服务:把四
个超类结合在一起,这样需要创建新小组件的用户无需记住全部混
入,也不用担心声明 class 语句时有没有遵守特定的顺序。Django
中的 ListView 类是更好的例子,稍后在 12.5 节讨论。

“优先使用对象组合,而不是类继承”

这句话引自《设计模式:可复用面向对象软件的基础》一书, 这
是我能提供的最佳建议。熟悉继承之后,就太容易过度使用它了。
出于对秩序的诉求,我们喜欢按整洁的层次结构放置物品,程序员
更是乐此不疲。

然而,优先使用组合能让设计更灵活。例如,对 tkinter.Widget
类来说,它可以不从全部几何管理器中继承方法,而是在小组件实
例中维护一个几何管理器引用,然后通过它调用方法。毕竟,小组
件“不是”几何管理器,但是可以通过委托使用相关的服务。这样,
我们可以放心添加新的几何管理器,不必担心会触动小组件类的层
次结构,也不必担心名称冲突。即便是单继承,这个原则也能提升
灵活性,因为子类化是一种紧耦合,而且较高的继承树容易倒。

组合和委托可以代替混入,把行为提供给不同的类,但是不能取代
接口继承去定义类型层次结构。

接下来,我们将从这些建议入手分析 Tkinter。

Tkinter好的、不好的和令人厌恶的方面

记住一点,自 1994 年发布的 Python 1.1 起,Tkinter 就在标准
库中了。Tkinter 的底层是 Tcl 语言优秀的 GUI 工具包 Tk。Tcl/Tk 组
合原本不是面向对象的,因此 Tk API 基本上就是一堆函数。尽管
没有使用面向对象方式实现,但是这个工具包的理念极具面向对象
思想。

前几节给出的建议 Tkinter 大都没有采用,不过第 7 点是个例外。但是
Tkinter 做得并不好,因为使用组合模式把几何管理器集成到 Widget 中
更好,如第 8 点所述。

tkinter.Widget 类的文档字符串开头说它是“内部类”。这或许表明
Widget 应该定义为抽象基类。Widget 自身虽然没有方法,但是它定义
了接口。它传达的意思是:“每个 Tkinter 小组件都会提供基本的方法
init、destroy,以及众多 Tk API 函数),此外还会提供三个
几何管理器中的全部方法。”你可以不同意这是定义接口的好方式(太
宽泛了),但是这样确实能定义接口,Widget 就把接口“定义”为超类
接口的联合。

封装 GUI 应用逻辑的 Tk 类继承自 Wm 和 Misc,这两个类既不是抽象
类,也不是混入(Wm 不算是混入,因为 TopLevel 的超类只有它一
个)。Misc 类的名称本身明显是代码异味。 Misc 有 100 多个方法,
而且所有小组件类都继承它。为什么每个小组件都要处理剪切板、文本
选择和计时器等?我们可能不能把文本粘贴到按钮上,也不能选择滚动
条里的文字。 Misc 应该拆分成几个专门的混入类,而且不是所有小组
件都应该继承这些混入。

说实在的,作为 Tkinter 的用户,你根本不用知道或使用多重继承。那
些都是隐藏起来的实现细节,你在自己的代码中只需实例化或子类化小
组件类。不过,如果你想查找自己需要的方法,在控制台中输入
dir(tkinter.Button),你会发现列出了 214 个属性, 此时你就是多
重继承的受害者。

除了这些问题,Tkinter 还是稳定而灵活的,未必那么不堪。陈旧(和默
认)的 Tk 小组件没有考虑现代的用户界面,但是 Python 3.1(2009 年发
布)提供了 tkinter.ttk 包,这个包提供的小组件很精美,外观同原
生的一样,开发出的 GUI 应用也更专业。此外,有些陈旧的小组件,如
Canvas 和 Text,功能异常强大。只需少量代码,就能把一个 Canvas
对象打造成简单的拖拽绘图应用。如果你对 GUI 编程感兴趣,Tkinter
和 Tcl/Tk 绝对值得一看。
然而,我们的主题不是 GUI 编程,而是多重继承的运用。显式使用混入
类的现代示例在 Django 中可以找到。

相关文章:

  • 达梦的三权分立安全机制
  • 微信小程序实现文字逐行动画效果渲染显示
  • 快速入门:创建 Azure 数据资源管理器群集和数据库
  • golang字符串拼接
  • pytest的装饰器`pytest.mark.parametrize` 和 `@pytest.mark.smoke`区别
  • 【C++】模拟实现map和set
  • JNDI注入入门
  • 互联网大厂Java求职面试:AI大模型应用实践中的架构挑战与实战
  • 探索niri:让你的Linux桌面布局无拘无束
  • 中年迷航,正念掌舵:在失业与转型中,找回内心的“定盘星”
  • 使用STM32设置GPIO中断
  • NLP学习路线图(四十六):可解释性
  • 华为云Flexus+DeepSeek征文 | 基于华为云Dify-LLM应用开发平台构建写作助手
  • 流程规划进阶——59页 15.流程的梳理方法【附全文阅读】
  • 标准库转hal库
  • JUC核心解析系列(一)——原子类深度解析
  • [C++] STL大家族之<map>(字典)容器(附洛谷)
  • 双重特征c++
  • 用于生成式新颖视图合成的密集 3D 场景完成
  • 字节FlowGram:AI时代可视化工作流的新范式
  • 如何做亚马逊国外网站/江西百度推广开户多少钱
  • vs做网站如何放背景图/郑州网络推广平台有哪些
  • 网站建设销售兼职合同/随州今日头条新闻
  • 网站用asp还是php/计算机基础培训机构
  • 深圳商城网站哪家做的好/郑州seo排名扣费
  • wordpress免费创建博客/seo文章生成器