ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python MRO方法解析顺序:C3线性化、super()与多继承实战

Python MRO方法解析顺序:C3线性化、super()与多继承实战 很多人第一次接触 python 中的 MRO是在一个看上去人畜无害的多继承类里翻的车代码明明写了父类方法调用时却跑到了另一个父类或者两个基类都定义了同名方法结果只有一个被触发另一个像被空气吃掉。更让人头大的是有时连类都创建不了直接甩出一句TypeError: Cannot create a consistent method resolution order。这类问题不是靠猜能解决的必须理解 Python 到底按什么顺序在继承链里找方法。MRO 就是那个顺序表它决定了属性、方法、super()在多个父类之间如何被解析。无论你是在学 python 基础、python 入门还是已经用 python 爬虫、python 数据分析与可视化写了几个项目只要碰到多继承、Mixin、框架里的类视图MRO 就是绕不过去的一关。今天这篇不打算停留在“记住从左到右”这种半吊子结论上而是把 C3 线性化、super()的真实含义、常见报错和实操排查一次讲透。你不需要先配置 vscode python 环境或者 pycharm 配置 python 环境才能看懂但最好打开一个能跑 Python 3 的解释器边看边敲效果比干读强得多。1. MRO到底在解决什么麻烦从多继承翻车现场说起1.1 单继承很直观多继承可能让方法“凭空消失”单继承的世界里方法查找基本没有悬念当前类没有就去父类找父类没有就去父类的父类找一路向上直到object。这种链式结构像家谱每个人只有一个直系父亲查找路径非常清楚。可一旦引入多继承一个类可以同时继承多个基类问题立刻复杂起来。比如class D(B, C)当D的实例调用foo()而B和C都定义了foo()Python 到底先用谁如果B的foo()又调用了super().foo()这个super()是去找C的foo()还是去找B的父类更极端一点如果B和C又共同继承自AA的方法会不会被执行两次这些都不是理论问题而是日常写框架、写 Mixin、写插件系统时随时会踩的坑。很多教程喜欢把多继承说成“从左到右、深度优先”这话在 Python 2 的经典类时代部分成立但在 Python 3 里已经不准确。Python 3 所有类默认都是新式类最终都继承object方法解析顺序由 C3 线性化算法生成。这个算法既要保证局部优先级比如子类先于父类又要保证基类声明顺序比如class D(B, C)里B优先于C还要保证单调性不能出现某个类在 MRO 里忽前忽后。正是这些约束让 MRO 有时会直接拒绝创建一个“自相矛盾”的类。理解这一点比背结论重要得多。从实操角度看MRO 问题最常出现在三类场景第一类是 Mixin 组合比如权限校验、日志记录、缓存、序列化这些横切功能第二类是框架类视图比如 Django CBV、DRF 的generics和mixins第三类是抽象基类和接口模拟比如collections.abc、abc.ABC、typing.Protocol。这些场景都有一个共同点开发者希望把多个可复用的小块拼成一个完整类而拼接顺序直接影响最终行为。所以 MRO 不是学院派概念它是多继承代码能不能稳定运行的地基。1.2 一句话说清MRO类的方法解析顺序MRO全称 Method Resolution Order中文通常叫“方法解析顺序”。它本质上是一个线性列表里面按顺序存放了当前类以及所有祖先类。当你在实例上访问某个属性或方法时Python 会沿着这个列表从头到尾查找找到第一个匹配的就停止。也就是说MRO 决定了“谁先被看到”。如果你打印D.__mro__会得到一个元组比如(D, B, C, A, object)这表示D的实例在找方法时先看D再看B再看C再看A最后看object。这里有一个非常关键但容易被忽略的点MRO 是类级别的不是实例级别的。每个类在创建时就会计算好自己的 MRO之后实例共享这个顺序。你可以在类定义完成后随时查看它但不要在运行时试图修改它。MRO 也不是简单的“父类列表”而是经过合并、去重、排序后的结果。比如D(B, C)的 MRO 里B和C的顺序通常保持声明顺序但共同祖先A会排到它们后面object永远在最后。这个顺序一旦确定super()、getattr、hasattr等机制都会依赖它。很多开发者误以为super()就是“调用父类方法”这个理解在单继承里勉强够用在多继承里会出大问题。super()真正的含义是“从当前类在 MRO 中的下一个类开始查找”。注意是当前类在 MRO 中的下一个位置不是当前类的某个父类。比如在B的方法里写super()而实例实际类型是D那么super()会从D的 MRO 中B之后的位置继续找。这个机制是协作式多继承的基础也是很多“为什么父类方法被跳过”问题的根源。后面会专门用代码演示。1.3 为什么每个Python开发者都该懂一点MRO如果你只写单继承或者只写组合确实可以长期不碰 MRO。但现实项目里多继承往往以更隐蔽的方式出现。你可能用了一个第三方库的类它内部已经有多层继承你可能写了一个 Mixin希望它自动生效你可能升级依赖后某个类的基类顺序变了导致你的方法不再被调用。这时候如果不懂 MRO就只能靠反复打印和试错效率极低。懂 MRO 的人会先看__mro__再决定是调整基类顺序、改super()调用还是干脆拆成组合。另一个现实原因是面试和代码评审。MRO 是 Python 面向对象部分的高频问题能说清 C3 合并规则的人通常对 Python 对象模型有更扎实的理解。但更重要的是它能帮你写出更可预测的代码。多继承不是洪水猛兽用得好可以极大减少重复比如把“记录日志”“检查权限”“缓存结果”拆成独立 Mixin用得不好则会制造一张谁也理不清的继承网。MRO 就是那张网的经纬线知道线怎么走才能决定要不要换一种编织方式。2. C3线性化拆解MRO不是简单的从左到右2.1 Python 2经典类到Python 3新式类的MRO变化要理解今天的 MRO最好先知道它为什么变成现在这样。Python 2 时代存在经典类和新式类两种。经典类不显式继承object它的方法查找采用深度优先、从左到右的策略。假设有class A、class B(A)、class C(A)、class D(B, C)经典类的查找顺序大致是D - B - A - C。注意A在C前面被访问这会导致一个问题如果A的方法想在多继承里被多个子类协作调用顺序会变得别扭而且同一个祖先可能被重复经过。经典类没有统一的object根行为也不够一致。Python 3 只保留新式类所有类最终继承objectMRO 由 C3 线性化算法计算。C3 的目标是生成一个满足局部优先级和单调性的线性列表。局部优先级包括子类永远在父类之前在class D(B, C)中B在C之前每个类的直接基类顺序必须保留。单调性则要求如果某个类在父类的 MRO 中排在另一个类前面那么在子类的 MRO 中也必须保持这个相对顺序。正是单调性约束让某些看似合理的继承结构变得非法因为 C3 无法同时满足所有顺序要求。这个变化对写代码的影响很直接在 Python 3 里你可以放心依赖super()在 MRO 中逐个传递只要每个协作方法都调用super()。这也是为什么现代框架大量使用 Mixin 和协作式多继承。它们不是简单地把功能“叠加”而是把方法调用串成一条链。链的顺序由 MRO 决定链的完整性由每个方法是否调用super()决定。理解这一点再看 Django 的LoginRequiredMixin、DRF 的CreateModelMixin就不会觉得它们神秘了。2.2 C3合并规则用排队类比理解head和tailC3 算法的核心是合并多个列表。每个类的 MRO 可以表示为L[类] [类] merge(L[基类1], L[基类2], ..., [基类1, 基类2, ...])。其中merge是一个操作它接收多个列表每次检查第一个列表的头部如果这个头部没有出现在任何其他列表的尾部就把它取出来作为结果的一部分如果出现了就跳过第一个列表检查下一个列表的头部如果所有列表的头部都无法选出说明存在顺序冲突直接抛出TypeError。这里有两个词要分清列表的“头部”是第一个元素“尾部”是除了头部之外的所有元素。比如列表[B, A, object]的头部是B尾部是[A, object]。为什么要求头部不出现在其他列表的尾部因为如果它出现在尾部说明另一个列表要求它排在某个元素后面。如果现在把它提前取出来就会违反那个列表的顺序。用排队类比几个人排成几队你想按某种顺序合并成一队。只有当某个队的队首没有出现在其他队的队尾时才能把他叫出来。如果他在别的队里还被要求排在某人后面那就不能先走否则会插队。这个规则保证了 MRO 的一致性和单调性。它看起来有点绕但手算几个例子就能掌握。实际开发中你不需要手写 C3 算法Python 已经内置了但排查 MRO 冲突时理解合并过程能帮你快速定位是哪两个基类的顺序要求打架了。尤其是当继承层级比较深、Mixin 比较多时凭空猜顺序几乎不可能手算或打印 MRO 才是正路。2.3 手算一个菱形继承的MRO最经典的例子是菱形继承D继承B和C而B和C都继承AA继承object。代码结构如下class A: pass class B(A): pass class C(A): pass class D(B, C): pass先写出各基类的 MRO。A的 MRO 是[A, object]。B(A)的 MRO 是[B] merge([A, object], [A])结果是[B, A, object]。同理C的 MRO 是[C, A, object]。现在计算D(B, C)L[D] [D] merge([B, A, object], [C, A, object], [B, C])第一步看第一个列表头部B。B是否出现在其他列表的尾部第二个列表尾部是[A, object]第三个列表尾部是[C]都没有B。所以取出B。结果变成[D, B]剩余列表为[A, object]、[C, A, object]、[C]。第二步看第一个列表头部A。A出现在第二个列表的尾部[A, object]中所以不能取。跳到下一个列表头部是C。C是否出现在其他列表尾部第一个列表尾部是[object]第二个列表尾部是[A, object]第三个列表尾部为空都没有C。取出C。结果变成[D, B, C]剩余列表为[A, object]、[A, object]、[]。第三步两个列表头部都是A且A不再出现在任何尾部取出A。最后取出object。最终 MRO 是[D, B, C, A, object]。你可以用print(D.__mro__)验证结果正是(class __main__.D, class __main__.B, class __main__.C, class __main__.A, class object)。注意A排在B和C后面而不是在B之后立刻出现。这个顺序解释了为什么super()在B中会跳到C而不是跳到A。如果你在B的方法里写super().foo()实例是D那么下一个查找目标是C不是A。2.4 制造一次MRO冲突看看TypeError怎么来的C3 并不是万能的有些继承结构因为顺序要求互相矛盾根本无法生成一致的 MRO。看下面这个例子class X: pass class Y: pass class A(X, Y): pass class B(Y, X): pass class C(A, B): passA要求X在Y前面因为class A(X, Y)。B要求Y在X前面因为class B(Y, X)。现在C(A, B)同时继承A和BC3 合并时会发现A的 MRO 里X在Y前B的 MRO 里Y在X前两个要求直接打架无法选出一个安全的头部。Python 会抛出类似这样的错误TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y这个报错不是 Python 在刁难你而是在保护你。因为如果强行选一个顺序必然违反某个基类的声明顺序导致方法解析结果不可预测。遇到这种错误不要试图给类加object或改继承方式蒙混过关。正确做法是审视继承关系是不是把两个本不该平级的类硬拼在一起了是不是应该用组合代替继承是不是基类顺序写反了大多数情况下拆掉其中一条继承线用组合持有另一个对象问题会立刻消失代码也更清晰。3. 代码实战查看、验证和使用MRO3.1 三种查看MRO的方式mro、mro()、inspect.getmro想知道一个类的 MRO最直接的方式是打印__mro__。它是一个元组包含当前类和所有祖先类顺序就是解析顺序。比如print(D.__mro__)。你也可以调用D.mro()它返回一个列表内容相同但类型是list。注意mro()是类方法通常只在类上调用实例上没有这个方法。如果你只有实例对象可以用type(obj).mro()或obj.__class__.mro()。另外标准库inspect提供了inspect.getmro(D)效果类似返回元组适合在需要统一工具函数时使用。class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(D.__mro__) print(D.mro()) print(D.__class__)实测下来D.__mro__输出(D, B, C, A, object)。D.mro()输出[D, B, C, A, object]。这三个方法没有本质区别但在不同场景下好用程度不同。调试时我喜欢用__mro__因为元组打印出来紧凑写工具函数时用inspect.getmro因为它对旧式类和新式类都做过兼容处理在代码里做动态判断时用mro()更方便做列表操作。记住一点不要在运行时修改 MRO也不要依赖内部实现去“重排”它。MRO 是类创建时确定的改不了也不该改。3.2 super()的真实含义MRO里的下一个目标super()是理解 MRO 的关键。很多教程说“super()调用父类方法”这个说法不严谨。准确地说super()返回一个代理对象它从当前类在 MRO 中的下一个类开始查找属性。当前类是哪个不是实例的类而是super()所在方法定义时所在的类。Python 3 的零参数super()依赖编译器插入的__class__单元格所以它知道当前方法定义在哪个类里。然后它结合实例的 MRO从当前类之后继续查找。看一个例子class A: def foo(self): print(A.foo) class B(A): def foo(self): print(B.foo) super().foo() class C(A): def foo(self): print(C.foo) super().foo() class D(B, C): pass D().foo()D的 MRO 是[D, B, C, A, object]。调用D().foo()时先找D没有foo找B有。执行B.foo打印B.foo然后super().foo()。这里的当前类是B实例是DMRO 中B的下一个是C。所以super().foo()调用的是C.foo打印C.foo再super().foo()当前类是C下一个是A调用A.foo。最终输出B.foo C.foo A.foo如果你把D(B, C)改成D(C, B)MRO 变成[D, C, B, A, object]输出顺序会变成C.foo、B.foo、A.foo。这说明基类声明顺序直接影响super()链。很多“方法被跳过”的问题本质是某个中间方法没有调用super()导致链条断裂。比如B.foo里不写super().foo()那么C.foo和A.foo都不会执行。这不是 MRO 算错了而是协作式调用被手动中断了。3.3 协作式初始化多继承中的__init__怎么写才不乱多继承里最麻烦的往往是__init__。每个基类都可能要初始化自己的状态如果直接写A.__init__(self)、B.__init__(self)很容易重复初始化或漏掉某个父类。协作式初始化要求每个__init__都调用super().__init__()并且只处理自己关心的参数把剩余参数透传下去。这样才能沿 MRO 形成一条完整的初始化链。class A: def __init__(self, **kwargs): print(A init) super().__init__(**kwargs) class B(A): def __init__(self, name, **kwargs): print(B init, name) self.name name super().__init__(**kwargs) class C(A): def __init__(self, age, **kwargs): print(C init, age) self.age age super().__init__(**kwargs) class D(B, C): def __init__(self, name, age): print(D init) super().__init__(namename, ageage) d D(Tom, 18) print(d.name, d.age)D的 MRO 是[D, B, C, A, object]。D.__init__调用super().__init__(name..., age...)进入B.__init__。B取出name把剩余age透传给super().__init__进入C.__init__。C取出age再把空kwargs传给super().__init__进入A.__init__。A打印后继续传给object.__init__此时没有多余参数不会报错。输出顺序是D init、B init、C init、A init。这套写法在框架中非常常见尤其是 DRF 的视图类。注意如果某个__init__不调用super()后面的类就不会初始化。如果object.__init__收到非空参数会抛TypeError所以参数要在到达object之前被消费完。使用**kwargs透传是常见做法但也要小心参数名冲突。3.4 用Mixin组织功能时顺序为什么比你想的重要Mixin 是一种特殊的多继承用法它通常不单独实例化只提供一小块可复用功能然后通过多继承混入目标类。Mixin 的顺序决定了它在 MRO 中的位置进而决定它的方法是否优先执行。比如一个权限校验 Mixin 和一个日志 Mixin都重写了dispatch谁先执行取决于谁写在前面。class AuthMixin: def dispatch(self, request, *args, **kwargs): print(auth check) return super().dispatch(request, *args, **kwargs) class LogMixin: def dispatch(self, request, *args, **kwargs): print(log request) return super().dispatch(request, *args, **kwargs) class View: def dispatch(self, request, *args, **kwargs): print(view dispatch) return response class MyView(AuthMixin, LogMixin, View): pass print(MyView.__mro__) print(MyView().dispatch(None))MyView的 MRO 是[MyView, AuthMixin, LogMixin, View, object]。调用dispatch时先进入AuthMixin打印auth check然后super()到LogMixin打印log request再到View打印view dispatch最终返回response。如果你把class MyView(LogMixin, AuthMixin, View)日志会先执行权限后执行。在真实项目里这个顺序可能意味着“先记录请求再做权限校验”还是“先权限校验再记录请求”。对于未登录请求前者会留下日志后者可能直接拒绝日志里什么也没有。所以 Mixin 顺序不是风格问题而是行为问题。4. 常见问题与排查技巧实录4.1 方法调用顺序和预期不一致按这四步排查遇到方法调用顺序不对先别急着改代码。第一步打印实例的实际类型的 MRO。注意是type(obj).__mro__不是obj.__class__.__mro__虽然两者通常一样但明确用type(obj)更直接。第二步找到出问题的方法在哪些类里定义了然后在 MRO 里标出这些类的先后顺序。第三步检查每个相关方法是否调用了super()以及super()调用时传了哪些参数。第四步确认实例化时用的类和你以为的类是否一致尤其是在工厂函数、框架动态生成类、装饰器包装之后实际类型可能不是你以为的那个。我踩过最典型的一次坑是一个缓存 Mixin 没有生效。打印 MRO 后发现缓存 Mixin 排在真实业务类后面而业务类自己重写了方法且没有调用super()导致缓存层根本没机会执行。解决办法不是改 MRO而是把缓存 Mixin 移到左边并让业务类的方法也调用super()。这件事让我养成一个习惯只要类里用了多继承类定义完成后立刻打印一次__mro__把它贴在代码旁边或写进注释后面排查会省很多时间。4.2 Cannot create a consistent MRO常见原因与修复表TypeError: Cannot create a consistent method resolution order通常不是语法错误而是继承顺序矛盾。下面这张表整理了常见原因和修复方向现象可能原因排查动作修复方式创建类时报 MRO 错误两个基类的祖先顺序要求相反分别打印两个基类的__mro__调整基类顺序或拆掉一条继承线菱形继承但共同祖先顺序异常多个基类都继承同一祖先且顺序不一致手算 C3 合并过程统一继承顺序或使用组合加入 Mixin 后报错Mixin 的基类与目标类基类冲突检查 Mixin 是否显式继承了不兼容的类让 Mixin 只继承object或调整 Mixin 基类动态创建类时报错type()动态生成的基类顺序矛盾打印动态基类列表重新排序基类避免循环依赖修复时优先考虑组合。如果两个类只是“需要一起工作”而不是“是同类事物的不同特化”用组合持有另一个对象通常更安全也不会产生 MRO 冲突。只有当多个类确实在同一继承体系里并且需要协作式调用时才值得保留多继承。4.3 多继承里的super()断链、参数冲突和重复调用super()断链是多继承中最隐蔽的问题之一。表现是某个中间类的方法没有执行或者初始化不完整。原因通常是某个类重写了方法但没有调用super()。排查方法很简单在 MRO 里找到断点前面的类检查它的方法里有没有super()。如果是第三方库的类看看文档是否要求你手动调用父类方法。另一种情况是参数冲突多个基类的__init__都需要不同参数协作式透传时参数名被覆盖。解决方式是使用**kwargs逐步消费或者改用显式的组合初始化。重复调用也很常见。比如有人为了“确保父类初始化”在子类里既写super().__init__()又写A.__init__(self)结果同一个祖先被初始化两次。在 MRO 中super()本来就会沿链走到每个类如果再硬编码调用某个祖先重复几乎不可避免。我的经验是只要用了多继承就统一走super()协作链不要在子类里直接写Base.__init__(self)除非你非常清楚自己在跳过哪一段。对于属性赋值、资源打开、事件注册这类有副作用的操作重复执行可能直接导致 bug。4.4 调试MRO时我常用的几个小技巧第一个技巧把 MRO 打印得好看一点。pprint对长继承链很有用from pprint import pprint pprint(MyView.__mro__)第二个技巧在方法里打印当前类名观察调用链class B(A): def foo(self): print(fenter {self.__class__.__name__} - {B.__name__}) super().foo()第三个技巧用inspect.getmro和inspect.getsource结合快速查看某个方法定义在哪个类。第四个技巧写单元测试验证 MRO 顺序而不是只靠肉眼看。比如assert MyView.__mro__.index(AuthMixin) MyView.__mro__.index(LogMixin)。这样以后有人调整基类顺序测试会立刻失败。第五个技巧遇到复杂继承结构时画一张继承图把 MRO 写在每个类旁边。这比在脑子里推导快得多也方便和同事沟通。5. MRO在真实项目里的影响范围与设计取舍5.1 框架中的Mixin顺序DRF、Django CBV和GUI库Django 的类视图和 DRF 的通用视图大量使用 Mixin。比如LoginRequiredMixin通常要放在View前面这样它的dispatch才能先执行拦截未登录请求。DRF 的CreateAPIView继承自CreateModelMixin和GenericAPIViewMRO 决定了post方法如何调用create以及perform_create在何时被触发。如果你自己写了一个审计 Mixin想记录所有创建操作就必须搞清楚它应该插在哪个位置。放在左边它可以在创建前执行放在右边可能创建已经完成才轮到它。GUI 库也类似。比如tkinter中经常用 Mixin 给窗口增加拖拽、快捷键、状态栏功能。Mixin 的顺序会影响事件绑定和默认行为覆盖。Web 框架、GUI 框架、ORM 的声明式 Mixin本质上都在利用 MRO 做“功能拼接”。这也是为什么很多框架文档会不厌其烦地提醒你Mixin 要放在基类左边。这不是风格建议而是 MRO 规则决定的硬性行为。5.2 MRO与抽象基类、Protocol、组合优先原则抽象基类ABC和typing.Protocol也会涉及 MRO。abc.ABC本身是一个类如果同时继承其他 ABCMRO 会决定抽象方法的覆盖关系。Protocol在运行时检查时也会用到 MRO尤其是判断某个类是否实现了协议要求的方法。不过在日常业务代码里抽象基类更多用于定义接口真正复杂的 MRO 问题还是来自多继承 Mixin。设计层面我一直建议“组合优先”。如果两个类的关系不是“is-a”而是“has-a”或“uses-a”就用组合。比如一个服务类需要日志功能可以持有一个 logger 对象而不是继承LogMixin。组合不会引入 MRO 复杂度依赖关系也更清晰。只有在需要框架自动发现、需要方法链协作、或者确实要复用一组行为的场景下才使用多继承。即使用了也尽量让 Mixin 保持无状态、方法简单、每个方法都调用super()。这样 MRO 即使变长行为也仍然可预测。5.3 给团队留下可读的继承链文档、测试和代码评审MRO 问题往往不是一个人写出来的而是多人协作、多次迭代后逐渐形成的。今天加一个 Mixin明天调整基类顺序后天升级框架版本继承链就可能变得面目全非。为了不让后来人踩坑我强烈建议在代码里留下显式信息。第一在复杂类定义上方用注释写出预期 MRO。第二写单元测试断言关键 Mixin 的相对顺序。第三在代码评审时只要看到多继承就要求作者说明基类顺序的理由。第四避免超过三层以上的多继承如果必须考虑拆分成更小的组合。这些做法看起来有点重但比起线上出现“权限校验没执行”“日志漏记”“初始化不完整”这类问题前期花几分钟写清楚非常划算。尤其是 Python 项目里经常混用第三方库你不一定知道某个基类内部是否还有复杂的继承关系。显式文档和测试能帮你在升级依赖时快速发现行为变化。5.4 性能与长期维护别让继承网变成迷宫从性能角度看MRO 本身在类创建时计算一次运行时方法查找是沿线性列表逐个匹配开销很小。真正的问题不在 CPU而在维护成本。一个类如果继承了很多基类MRO 可能有十几项光看代码根本不知道调用某个方法时会走到哪里。长期维护时这种不确定性会拖慢开发速度增加回归风险。我见过一个项目里一个核心类同时混入了权限、缓存、序列化、审计、限流五个 Mixin后来要改一个行为必须把整个 MRO 打印出来再逐层跟踪super()调用十分痛苦。所以MRO 知识不仅要用来解决报错更要用来指导设计。当你准备加第六个 Mixin 时先停下来问自己这些功能真的需要同时混入吗能不能拆成装饰器、中间件、组合对象如果必须保留能不能把顺序和依赖写成测试MRO 是工具不是目标。让继承链保持短、清晰、可验证比炫耀多继承技巧有价值得多。我现在接手多继承代码时第一件事通常不是读方法实现而是先跑一句print(SomeClass.__mro__)把顺序贴在编辑器旁边。只要顺序清楚了super()会走到哪里、Mixin 谁先谁后、哪个基类的方法会被覆盖基本就一目了然。如果打印出来的 MRO 长得让我需要滚动屏幕那我就会认真考虑重构而不是继续往上加类。多继承可以很强大但它的前提是你得清楚每一步为什么这样走。
返回列表