從零開始學Python-Day40-繼承和多態

面向對象編程過程中,我們可以從已有class繼承,定義一個新的類class,這個類被稱作子類Subclass,被繼承者的類被稱為基類、父類或者超類(Base class、Super class)。例如我們編寫了一個名為Animal的類:

<code>>>> class Animal(object):
	def run(self):
		print('Animals are running...')
>>> animal = Animal()
>>> animal.run()
Animals are running.../<code>

當我們定義新的類Dog和Cat時,可以直接從Animal繼承:

<code>>>> class Dog(Animal):
	pass
 
>>> class Cat(Animal):
	pass
 
>>> dog = Dog()
>>> dog.run()
Animals are running...
>>> cat = Cat()
>>> cat.run()
Animals are running.../<code>

對於Dog和Cat,Animal就是它們的父類,它倆就是Animal的子類,都繼承了Animal的run方法當然可以對子類增加方法:

<code>>>> class Dog(Animal):
	def run(self):
		print('Dog is running...')
	def eat(self):
		print('Dog is eating...')/<code>

相比直接從Animal繼承的run,顯然修改後的Dog類的run方法更符合邏輯。當子類和父類都存在相同方法run()時,子類的run覆蓋了父類的run。

多態

當我們定義一個class時,實際上就是定義了一種數據類型,接下來我們看下面這組判斷:

<code>>>>a = list() 
>>>b = Animal() 
>>>c = Dog()
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)
True/<code>

isinstance用來判斷一個變量是否為某個類型,我們看到a,b,c都符合對應的類型;但是c除了對應Dog類型,同時也是Animal類型。如上,在繼承關係中,如果一個實例的數據類型是一個子類,它的數據類型也可以看做父類,但反過來是不行的:

<code>>>> b = Animal()
>>> isinstance(b, Dog)
False/<code>

要理解多態的好處,我們還需要再編寫一個函數,這個函數接受一個Animal類型的變量:

<code>>>> def run_twice(animal):
	animal.run()
	animal.run()
 
>>> run_twice(Animal())
Animals are running...
Animals are running...
>>> run_twice(Dog())
Dog is running...
Dog is running.../<code>

我們再定義一個Animal的子類Mouse:

<code>>>> class Mouse(Animal):
	def run(self):
		print('Mouse is so small...')
 
		
>>> run_twice(Mouse())
Mouse is so small...
Mouse is so small.../<code>

可以看到對於新增的子類Mouse,不必修改run_twice,依賴父類Animal作為參數的函數或方法都可以不加修正的正常使用,原因就是多態!多態的好處就在於此,我們要傳入子類時,只要接收父類就可以,例如Dog、Cat都是Animal類型。對於一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:

1、對擴展開放:允許新增Animal子類;
2、對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。

繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關係。而任何類,最終都可以追溯到根類object,這些繼承關係看上去就像一顆倒著的樹。比如如下的繼承樹:

<code>                ┌───────────────┐
                │              object            │
                └───────────────┘
                                          │
           ┌────────────┴────────────┐
           │                                                            │
           ▼                                                           ▼
    ┌─────────────┐           ┌─────────────┐
    │          Animal          │           │            Plant           │
    └─────────────┘           └─────────────┘
           │                         │
     ┌─────┴──────┐            ┌─────┴──────┐
     │                             │            │                             │
     ▼                           ▼            ▼                            ▼
┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
│        Dog       │  │        Cat        │  │       Tree       │  │      Flower     │
└─────────┘  └─────────┘  └─────────┘  └─────────┘/<code>

靜態語言 vs 動態語言

對於靜態語言(如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:

<code>class Timer(object):
    def run(self):
        print('Start...')/<code>

這就是動態語言的“鴨子類型”,它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。Python的“file-like object“就是一種鴨子類型。對真正的文件對象,它有一個read()方法,返回其內容。但是,許多對象,只要有read()方法,都被視為“file-like object“。許多函數接收的參數就是“file-like object“,你不一定要傳入真正的文件對象,完全可以傳入任何實現了read()方法的對象。

小結

繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫。動態語言的鴨子類型特點決定了繼承不像靜態語言那樣是必須的。


分享到:


相關文章: