Python隨手筆記(八)--------面向對象編程(4)

8.4 多態

當子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,總是會調用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態。
要理解什麼是多態,我們首先要對數據類型再作一點說明。當我們定義一個class的時候,我們實際上就定義了一種數據類型。我們定義的數據類型和Python自帶的數據類型,比如str、list、dict沒什麼兩樣:

a = list() # a是list類型
b = Animal() # b是Animal類型
c = Dog() # c是Dog類型

判斷一個變量是否是某個類型可以用isinstance()判斷:

>>> isinstance(a,list)
True
>>> isinstance(b,Animal)
True
>>> isinstance(c,Dog)
True

然後我們就會發現下面這個例子:

>>> isinstance(c,Animal)
True

這是有道理的,因爲Dog是從Animal繼承下來的,當我們創建了一個Dog的實例c時,我們認爲c的數據類型是Dog沒錯,但c同時也是Animal,Dog本來就是Animal的一種。
所以,在繼承關係中,如果一個實例的數據類型是某個子類,那它的數據類型也可以被看做是父類。但是,反過來就不行:

>>> isinstance(b,Dog)
False

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

>>> def run_twice(animal):
	animal.run()
	animal.run()

當我們傳入Animal的實例時,run_twice()就打印出:

>>> run_twice(Animal())
Animal is runnning...
Animal is runnning...
>>> run_twice(b)
Animal is runnning...
Animal is runnning...

當我們傳入Dog的實例,run_twice()就會打印出:

>>> run_twice(c)
Dog is runnning...
Dog is runnning...

看上去沒啥意思,但如果我們再定義一個Tortoise類型,也從Animal派生,並創建tortoise的一個實例t:

>>> class Tortoise(Animal):
	def run(self):
		print('Tortoise is running slowly...')

		
>>> t = Tortoise()

調用run_twice函數,並將t傳入:


>>> run_twice(t)
Tortoise is running slowly...
Tortoise is running slowly...

會發現,新增一個Animal的子類,不必對run_twice()做任何修改,實際上,任何依賴Animal作爲參數的函數或者方法都可以不加修改地正常運行,原因就在於多態。
多態的好處就是,當我們需要傳入Dog、Cat、Tortoise……時,我們只需要接收Animal類型就可以了,因爲Dog、Tortoise……都是Animal類型,然後,按照Animal類型進行操作即可。由於Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多態的意思,而具體調用的run()方法是作用在Animal、Dog還是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章