继承(Inheritance) VS 合成(Composition)
什么是继承
继承的用处,就是用来指明一个类的大部分或全部功能,都是从一个父类中获得的。当你写 classFoo(Bar) 时,代码就发生了继承效果,这句代码的意思是“创建一个叫 Foo 的类,并让他继承Bar”。当你这样写时,Python 语言会让 Bar 的实例所具有的功能都工作在 Foo 的实例上。这样可以让你把通用的功能放到 Bar 里边,然后再给 Foo 特别设定一些功能。
当你这么做的时候,父类和子类有三种交互方式:
1. 子类上的动作完全等同于父类上的动作
2. 子类上的动作完全改写了父类上的动作
3. 子类上的动作部分变更了父类上的动作
我将通过代码向你一一展示。
隐式继承(Implicit Inheritance)
首先我将向你展示当你在父类里定义了一个函数,但没有在子类中定义的例子,这时候会发生隐式继承。
class Parent(object): # 新建父类
def implicit(self): # 父类函数
print("PARENT implicit()")
class Child(Parent): # 新建子类——隐式继承
pass # 空代码,表明子类没有定义新函数,继承了父类
dad = Parent()
son = Child()
dad.implicit() # 父类输出
son.implicit() # 子类输出
#运行结果
PARENT implicit()
PARENT implicit()
class Child: 中的 pass 是在 Python 中创建空的代码区块的方法。这样就创建了一个叫 Child 的类,但没有在里边定义任何细节。在这里它将会从它的父类中继承所有的行为。运行起来就是这样:
PARENT implicit()
PARENT implicit()
就算我在第 16 行调用了 son.implicit() 而且就算 Child 中没有定义过 implicit 这个函数,这个函数依然可以工作,而且和在父类 Parent 中定义的行为一样。这就说明,如果你将函数放到基类中(也就是这里的 Parent ),那么所有的子类(也就是 Child 这样的类)将会自动获得这些函数功能。如果你需要很多类的时候,这样可以让你避免重复写很多代码。
显式覆写(Explicit Override)
有时候你需要让子类里的函数有一个不同的行为,这种情况下隐式继承是做不到的,而你需要覆写子类中的函数,从而实现它的新功能。你只要在子类 Child 中定义一个相同名称的函数就可以了,如下所示:
class Parent(object): # 新建父类
def overside(self): # 父类函数
print("父类——PARENT override()")
class Child(Parent): # 新建子类
def overside(self): # 在子类中定义和父类相同的函数,为显示覆写
print("子类——CHILD override()")
dad = Parent()
son = Child()
dad.overside() # 父类输出
son.overside() # 子类输出
#运行结果
父类——PARENT override()
子类——CHILD override()
本例子子类中新定义的函数在这里取代了父类里的函数。
在运行前或运行后覆写
第三种继承的方法是一个覆写的特例,这种情况下,你想在父类中定义的内容运行之前或者之后再修改行为。首先你像上例一样覆写函数,不过接着你用 Python 的内置函数 super 来调用父类 Parent 里的版本。我们还是来看例子吧:
class Parent(object): # 新建父类
def altered(self): # 父类函数
print("父类——PARENT override()")
class Child(Parent): # 新建子类
def altered(self): # 在子类中定义和父类相同的函数,为显示覆写
print("子类——CHILD BEFORE PARENT altered)")
super(Child,self).altered() # 先运行父类,再改写
print("super——CHILD AFTER PARENT altered)")
dad = Parent()
son = Child()
dad.altered() # 父类输出
son.altered() # 子类输出
#运行结果
父类——PARENT override()
子类——CHILD BEFORE PARENT altered)
父类——PARENT override()
super——CHILD AFTER PARENT altered)
为什么要用 super()
到这里也算是一切正常吧,不过接下来我们就要来应对一个叫多重继承(Multiple Inheritance)的麻烦东西。多重继承是指你定义的类继承了多个类,就像这样:
class SuperFun(Child, BadStuff):
pass
这相当于说“创建一个叫 SuperFun 的类,让它同时继承 Child 和 BadStuff ”。这里一旦你在 SuperFun 的实例上调用任何隐式动作,Python 就必须回到类的层次结构中去检查Child 和 BadStuff ,而且必须要用固定的次序去检查。为实现这一点 Python 使用了一个叫 “方法解析顺序(Method Resolution Order,MRO)”的东西,还用了一个叫 C3 的算法。由于有这个复杂的 MRO 和这个很好的算法,Python 总不该把这些事情留给你去做吧,不然你不就跟着头大了?所以 Python 给你这个 super() 函数,用来在各种需要修改行为的场合为你处理,就像上面Child.altered 一样。有了 super() ,妈妈再也不用担心我吧继承关系弄糟,因为 Python 会给我找到正确的函数。
super() 和 __init__ 搭配使用
最常见的 super() 的用法是在基类的 __init__ 函数中使用。通常这也是唯一可以进行这种操作的地方,在这里你在子类里做了一些事情,然后完成对父类的初始化。这里是一个在 Child 中完成上述行为的例子:
class Child(Parent): # 新建子类
def altered(self): # 在子类中定义和父类相同的函数,为显示覆写
super(Child,self).__init__() # 先运行父类,再改写
print("super——CHILD AFTER PARENT altered)")
这和上面的 Child.altered 差别不大,只不过我在 __init__ 里边先设了个变量,然后才用Parent.__init__ 初始化了 Parent 。
class Other(object):
def override(self):
print("覆写-other override()")
def implicit(self):
print("隐式继承-other implicit()")
def altered(self):
print("other altered()")
class Child(object):
def __init__(self):
self.other = Other()
def implicit(self):
self.other.implicit()
def override(self):
print("Child override()")
def altered(self):
print("子类,before other altered()")
self.other.altered()
print("子类,after other altered()")
son = Child()
son.implicit()
son.override()
son.altered()
这里我没有使用 Parent 这个名称,因为这里不是父类子类的“A 是 B”的关系,而是一个“A 里有 B”的关系,这里 Child 里有一个 Other 用来完成它的功能。运行的时候,我们可以看到这样的输出:
隐式继承-other implicit()
Child override()
子类,before other altered()
other altered()
子类,after other altered()
继承和合成的应用场合
“继承 vs 合成”的问题说到底还是关于代码重用的问题。你不想到处都是重复的代码,这样既难看又没效率。继承可以让你在基类里隐含父类的功能,从而解决了这个问题。而合成则是利用模块和别的类中的函数调用实现了相同的目的。
如果两种方案都能解决重用的问题,那什么时候该用哪个呢?这个问题答案其实是非常主观的,不过我可以给你三个大体的指引方案:
1. 不惜一切代价地避免多重继承,它带来的麻烦比能解决的问题都多。如果你非要用,那你得准备好专研类的层次结构,以及花时间去找各种东西的来龙去脉吧。
2. 如果你有一些代码会在不同位置和场合应用到,那就用合成来把它们做成模块。
3. 只有在代码之间有清楚的关联,可以通过一个单独的共性联系起来的时候使用继承,或者你受现有代码或者别的不可抗拒因素所限非用不可的话,那也用吧。
然而,不要成为这些规则的奴隶。面向对象编程中要记住的一点是,程序员创建软件包,共享代码,这些都是一种社交习俗。由于这是一种社交习俗,有时可能因为你的工作同事的原因,你需要打破这些规则。这时候,你就需要去观察别人的工作方式,然后去适应这种场合。
PEP 0 – Index of Python Enhancement Proposals (PEPs) | peps.python.org
常见问题回答:
怎样增强自己解决新问题的技术?
提高解决问题能力的唯一方法就是自己去努力解决尽可能多的问题。很多时候人们碰到难题就会跑去找人给出答案。当你手头的事情非要完成不可的时候,这样做是没有问题的,不过如果你有时间自己解决的话,那就花时间去解决吧。停下手上的活,专注于你的问题死磕,试着用所有可能的方法去解决,不管最后解决与否都要试到山穷水尽为止。经过这样的过程,你找到的答案会让你更为满意,而你的解决问题的能力也提高了。
ps:本书的习题43,以及后面的习题目前来说都挺难的,本书的分享就先到这里了。学习路漫漫,仍需多多努力。