在OOP程序設(shè)計(jì)中竞思,當(dāng)我們定義一個(gè)class的時(shí)候踩娘,可以從某個(gè)現(xiàn)有的class繼承刮刑,新的class稱為子類(Subclass),而被繼承的class稱為基類养渴、父類或超類(Base class雷绢、Super class)。
比如厚脉,我們已經(jīng)編寫(xiě)了一個(gè)名為Animal
的class习寸,有一個(gè)run()
方法可以直接打印:
class Animal(object):
def run(self):
print('Animal is running...')
當(dāng)我們需要編寫(xiě)Dog
和Cat
類時(shí)傻工,就可以直接從Animal
類繼承:
class Dog(Animal):
pass
class Cat(Animal):
pass
對(duì)于Dog
來(lái)說(shuō)霞溪,Animal
就是它的父類孵滞,對(duì)于Animal
來(lái)說(shuō),Dog
就是它的子類鸯匹。Cat
和Dog
類似坊饶。
繼承有什么好處?最大的好處是子類獲得了父類的全部功能殴蓬。由于Animial
實(shí)現(xiàn)了run()
方法匿级,因此,Dog
和Cat
作為它的子類染厅,什么事也沒(méi)干痘绎,就自動(dòng)擁有了run()
方法:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
運(yùn)行結(jié)果如下:
Animal is running...
Animal is running...
當(dāng)然,也可以對(duì)子類增加一些方法肖粮,比如Dog類:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
繼承的第二個(gè)好處需要我們對(duì)代碼做一點(diǎn)改進(jìn)孤页。你看到了,無(wú)論是Dog
還是Cat
涩馆,它們run()
的時(shí)候行施,顯示的都是Animal is running...
,符合邏輯的做法是分別顯示Dog is running...
和Cat is running...
魂那,因此蛾号,對(duì)Dog
和Cat
類改進(jìn)如下:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
再次運(yùn)行,結(jié)果如下:
Dog is running...
Cat is running...
當(dāng)子類和父類都存在相同的run()
方法時(shí)涯雅,我們說(shuō)鲜结,子類的run()
覆蓋了父類的run()
,在代碼運(yùn)行的時(shí)候斩芭,總是會(huì)調(diào)用子類的run()
轻腺。這樣,我們就獲得了繼承的另一個(gè)好處:多態(tài)划乖。
要理解什么是多態(tài),我們首先要對(duì)數(shù)據(jù)類型再作一點(diǎn)說(shuō)明挤土。當(dāng)我們定義一個(gè)class的時(shí)候琴庵,我們實(shí)際上就定義了一種數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型仰美,比如str迷殿、list、dict沒(méi)什么兩樣:
a = list() # a是list類型
b = Animal() # b是Animal類型
c = Dog() # c是Dog類型
判斷一個(gè)變量是否是某個(gè)類型可以用isinstance()
判斷:
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
看來(lái)a
咖杂、b
庆寺、c
確實(shí)對(duì)應(yīng)著list
、Animal
诉字、Dog
這3種類型懦尝。
但是等等知纷,試試:
>>> isinstance(c, Animal)
True
看來(lái)c
不僅僅是Dog
,c
還是Animal
陵霉!
不過(guò)仔細(xì)想想琅轧,這是有道理的,因?yàn)?code>Dog是從Animal
繼承下來(lái)的踊挠,當(dāng)我們創(chuàng)建了一個(gè)Dog
的實(shí)例c
時(shí)乍桂,我們認(rèn)為c
的數(shù)據(jù)類型是Dog
沒(méi)錯(cuò),但c
同時(shí)也是Animal
也沒(méi)錯(cuò)效床,Dog
本來(lái)就是Animal
的一種睹酌!
所以,在繼承關(guān)系中剩檀,如果一個(gè)實(shí)例的數(shù)據(jù)類型是某個(gè)子類憋沿,那它的數(shù)據(jù)類型也可以被看做是父類。但是谨朝,反過(guò)來(lái)就不行:
>>> b = Animal()
>>> isinstance(b, Dog)
False
Dog
可以看成Animal
卤妒,但Animal
不可以看成Dog
。
要理解多態(tài)的好處字币,我們還需要再編寫(xiě)一個(gè)函數(shù)则披,這個(gè)函數(shù)接受一個(gè)Animal
類型的變量:
def run_twice(animal):
animal.run()
animal.run()
當(dāng)我們傳入Animal
的實(shí)例時(shí),run_twice()
就打印出:
>>> run_twice(Animal())
Animal is running...
Animal is running...
當(dāng)我們傳入Dog
的實(shí)例時(shí)洗出,run_twice()
就打印出:
>>> run_twice(Dog())
Dog is running...
Dog is running...
當(dāng)我們傳入Cat
的實(shí)例時(shí)士复,run_twice()
就打印出:
>>> run_twice(Cat())
Cat is running...
Cat is running...
看上去沒(méi)啥意思,但是仔細(xì)想想翩活,現(xiàn)在阱洪,如果我們?cè)俣x一個(gè)Tortoise
類型,也從Animal
派生:
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
當(dāng)我們調(diào)用run_twice()
時(shí)菠镇,傳入Tortoise
的實(shí)例:
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
你會(huì)發(fā)現(xiàn)冗荸,新增一個(gè)Animal
的子類,不必對(duì)run_twice()
做任何修改利耍,實(shí)際上蚌本,任何依賴Animal
作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行,原因就在于多態(tài)隘梨。
多態(tài)的好處就是程癌,當(dāng)我們需要傳入Dog
、Cat
轴猎、Tortoise
……時(shí)嵌莉,我們只需要接收Animal
類型就可以了,因?yàn)?code>Dog捻脖、Cat
锐峭、Tortoise
……都是Animal
類型中鼠,然后,按照Animal
類型進(jìn)行操作即可只祠。由于Animal
類型有run()
方法兜蠕,因此,傳入的任意類型抛寝,只要是Animal
類或者子類熊杨,就會(huì)自動(dòng)調(diào)用實(shí)際類型的run()
方法,這就是多態(tài)的意思:
對(duì)于一個(gè)變量盗舰,我們只需要知道它是Animal
類型晶府,無(wú)需確切地知道它的子類型,就可以放心地調(diào)用run()
方法钻趋,而具體調(diào)用的run()
方法是作用在Animal
川陆、Dog
、Cat
還是Tortoise
對(duì)象上蛮位,由運(yùn)行時(shí)該對(duì)象的確切類型決定较沪,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用,不管細(xì)節(jié)失仁,而當(dāng)我們新增一種Animal
的子類時(shí)尸曼,只要確保run()
方法編寫(xiě)正確,不用管原來(lái)的代碼是如何調(diào)用的萄焦。這就是著名的“開(kāi)閉”原則:
對(duì)擴(kuò)展開(kāi)放:允許新增Animal
子類控轿;
對(duì)修改封閉:不需要修改依賴Animal
類型的run_twice()
等函數(shù)。
繼承還可以一級(jí)一級(jí)地繼承下來(lái)拂封,就好比從爺爺?shù)桨职植缟洹⒃俚絻鹤舆@樣的關(guān)系。而任何類冒签,最終都可以追溯到根類object在抛,這些繼承關(guān)系看上去就像一顆倒著的樹(shù)。比如如下的繼承樹(shù):
┌───────────────┐
│ object │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Animal │ │ Plant │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Cat │ │ Tree │ │ Flower │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
靜態(tài)語(yǔ)言 vs 動(dòng)態(tài)語(yǔ)言
對(duì)于靜態(tài)語(yǔ)言(例如Java)來(lái)說(shuō)萧恕,如果需要傳入Animal
類型霜定,則傳入的對(duì)象必須是Animal
類型或者它的子類,否則廊鸥,將無(wú)法調(diào)用run()
方法。
對(duì)于Python這樣的動(dòng)態(tài)語(yǔ)言來(lái)說(shuō)辖所,則不一定需要傳入Animal
類型惰说。我們只需要保證傳入的對(duì)象有一個(gè)run()
方法就可以了:
class Timer(object):
def run(self):
print('Start...')
這就是動(dòng)態(tài)語(yǔ)言的“鴨子類型”,它并不要求嚴(yán)格的繼承體系缘回,一個(gè)對(duì)象只要“看起來(lái)像鴨子吆视,走起路來(lái)像鴨子”典挑,那它就可以被看做是鴨子。
Python的“file-like object“就是一種鴨子類型啦吧。對(duì)真正的文件對(duì)象您觉,它有一個(gè)read()
方法,返回其內(nèi)容授滓。但是琳水,許多對(duì)象,只要有read()
方法般堆,都被視為“file-like object“在孝。許多函數(shù)接收的參數(shù)就是“file-like object“,你不一定要傳入真正的文件對(duì)象淮摔,完全可以傳入任何實(shí)現(xiàn)了read()
方法的對(duì)象私沮。
小結(jié)
繼承可以把父類的所有功能都直接拿過(guò)來(lái),這樣就不必重零做起和橙,子類只需要新增自己特有的方法仔燕,也可以把父類不適合的方法覆蓋重寫(xiě)。
動(dòng)態(tài)語(yǔ)言的鴨子類型特點(diǎn)決定了繼承不像靜態(tài)語(yǔ)言那樣是必須的魔招。