前言
面向對象是所有高級語言(Python,Java,C++等)的基石,是重中之重。
這個文章系列的目的是通過簡單易懂的例子,深入淺出,讓Python學習者牢固的掌握Python面向對象的概念和方法。
本系列包括:
面向對象(1) - 屬性,構造方法,self關鍵詞。
面向對象(2) - 實例方法 (本文)
面向對象(3) - 類屬性和類方法
面向對象(4) - 繼承和多態,以及一個綜合小遊戲案例
類的繼承,父類,子類
看這張圖:
上面有一條小狗,代表了普通的狗。
下面有警犬,牧羊犬和寵物犬,他們都是狗。
但他們又有獨特的特點,具有額外的屬性或者技能。
警犬有偵探功能,所以有detect()方法,有額外的屬性ability,表示它的偵探能力等級。
牧羊犬有保護養的功能,所以有protect()方法,有額外的num_of_sheeps表示它可以保護幾隻羊。
寵物犬有唱歌的功能,所以有sing()方法,有額外的屬性price表示價格。
動物界具有天然的繼承關係,人類也是,我們一代代繼承下來。繼承了前輩們的屬性和能力,又發展了自己獨特的屬性和能力。
在圖中的例子,我們如何在程序中表示普通的狗,牧羊犬,警犬等呢?
我們可以把detect(), protect()等函數和屬性直接加在Dog裡面,但這並不合理,因為並不是所有的狗可以偵查,並不是所有的狗都可以保護養。
正確的作法是創建新的類,這些新的類繼承Dog類:
SheepDog 表示牧羊犬
PoliceDog 表示警犬
PetDog 表示寵物犬
在這裡Dog被稱為父類,SheepDog等被稱為子類。
子類會自動擁有父類的屬性和方法,自己也可以添加自己的獨特屬性和方法。
子類的定義
現在來定義SheepDog。先看看我們原來的Dog類:
定義子類SheepDog
仔細閱讀上面的代碼,觀察它的特點:
聲明繼承關係
SheepDog(Dog)這種寫法:括號中的Dog表示Dog是SheepDog的父類。
我們定義Dog的時候沒有括號,表示它沒有父類(實際上它默認繼承了Object類)。
構造函數和super()方法
子類也有構造函數init方法,傳入各項必要的屬性。
構造函數的第一行調用父類的構造函數,因為牧羊犬首先也是一個Dog,所以要先構造一個Dog出來。
其中super()代表父類。我們用self表示自己,用super()表示父類。注意self是一個關鍵詞,是沒有括號的,super()是一個函數,是有括號的。
值得注意的是,使用self可以訪問父類的屬性和方法,因為父類的就是自己的。在構造函數中我們使用super()來調用父類的構造函數是因為子類和父類都有__init__方法,如果不適用super()就會調用自己的__init__方法,這種情況下需要用super)()來明確指定要訪問父類的方法。
第二行添加了牧羊犬的獨特屬性num_of_sheeps。這樣牧羊犬有4個屬性,其中3個屬性是繼承自父類的,一個是自己獨有的。
使用子類
子類的使用和父類是一樣的:
調用構造函數創建實例。
通過變量名訪問屬性。
通過變量名調用方法。注意bark()是父類的函數,子類可以直接使用。
給子類添加新的方法
我們給SheepDog添加它的獨特方法protect():
調用一下試試看:
覆蓋父類的方法
因為繼承的關係,SheepDog直接就有bark()方法,這是從父類繼承過來的。
假設牧羊犬的叫聲和普通叫聲是不一樣的,我們在子類中覆蓋父類中的方法:
這時候再調用bark()方法就會使用子類中定義的方法:
列印的結果是:
集成和覆蓋的用處舉例
類的繼承和對父類方法的覆蓋在代碼設計中很有用。假設有個程序的界面是這樣的:
按鈕就是一個類,比如叫做Button。
為了實現不同的皮膚,我們可以寫一個類繼承Button類,假設就叫做MyButton吧,子類自動擁有了父類的屬性和函數,但是我們可以覆蓋某些函數,讓他擁有不同的皮膚,甚至不同的行為。
Dog版本的吃雞遊戲
面向對象的核心知識到這裡就更新完了,最後奉上Dog版本的吃雞遊戲。這個遊戲包含兩個類:
其中dog.py定義了幾個狗類
game.py中創建100條Dog並讓他們互相攻擊,直到最後只有一隻為止。
dog.py
game.py