「老豆」,粵俚,老爸也。
Hardwood
(闊葉樹)和Conifer
(針葉樹)均繼承自Tree
。後來發現另外有一種樹Timba
既屬於Hardwood
,也屬於Conifer
,但又有它自己的特性,所以乾脆就同時繼承Hardwood
和Conifer
。Tree
, Hardwood
, Conifer
和Timba
四個類別之間的關係,圖示如下:#include <iostream>
using namespace std;
class Tree{
public:
string breed;
Tree(string myBreed){
breed = myBreed;
cout << "Tree's constructor. I am a " << this->breed << '.' << endl;
}
};
class Hardwood : public Tree{ // 繼承自Tree。
public:
Hardwood(string breed): Tree(breed){
cout << "Hardwood's constructor." << endl << endl;
}
};
class Conifer : public Tree{ // 繼承自Tree。
public:
Conifer(string breed): Tree(breed){
cout << "Conifer's constructor." << endl << endl;
}
};
class Timba : public Hardwood, public Conifer{ // 同時繼承自Hardwood及Conifer。
public:
Timba(string breed): Hardwood(breed), Conifer(breed){
cout << "Timba's constructor." << endl;
}
};
int main(void){
Timba timba("timba");
return 0;
}
Python是支援多重繼承的。以下沿用之前的Tree
, Hardwood
, Conifer
和Timba
四個類別關係,改用Python撰寫。為了聚焦於繼承,所有屬性都設成公開,省去寫property的「麻煩」:
class Tree():
def __init__(self, breed: str, age: int): # constructor
self.breed = breed
self.age = age
def show_info(self):
print(f'{self.breed=:10}{self.age=:<10,}')
class Hardwood(Tree):
def __init__(self, breed: str, age: int, defoliation: dict, puberty: dict): # defoliation是落葉,puberty是開花期。
super().__init__(breed, age) # 呼叫父類別的constructor。
self.defoliation = defoliation # 子類別自己的屬性。
self.puberty = puberty # 子類別自己的屬性。
class Conifer(Tree):
def __init__(self, breed: str, age: int):
super().__init__(breed, age) # 呼叫父類別的constructor。
# self.price = price # 子類別自己的屬性。
def sell(self, seller: str, buyer) -> bool: # 子類別自己的方法。
... # 實作略
print(f'{seller=:20}{buyer=:20}')
return True
class Timba(Hardwood, Conifer):
def __init__(self, breed: str, age: int, defoliation: dict, puberty: dict, durability: int, strength: int):
super().__init__(breed, age, defoliation, puberty) # 呼叫父類別的constructor。
self.durability = durability
self.strength = strength
輸出:
噢,我好像不小心走錯路了。一來就用了一個過於複習的例子,這絕對不是好的展示方式。
就讓我們再走一遍,從最簡單的出發吧。為了簡化,以下的各個情境全省去屬性,只用方法來說明Python的多重繼承機制。
情境-1:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
... # 完全不實作,看效果如何。
tree = Timba('timba')
tree.show_breed()
輸出:
此情境Timba
類別沒有自己的實作,所以在呼叫show_breed()
時會往上找其父類別。但它有兩個父親,而且兩個老爸都有實作show_breed(),而Timba類別是這樣定義的:class Timba(Hardwood, Conifer)
。Python呼叫父類別方法的原則是:排在越前面的越優先。所以本情境實際呼叫到的是Hardwood
的show_breed()
而不是Conifer的。
假如Timba
類別的繼承順序調過來,Conifer
放在前面,這時呼叫到的當然就是Conifer
的show_breed()
了。
情境-2:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
... # 不實作。
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
... # 完全不實作,看效果如何。
tree = Timba('timba')
tree.show_breed()
輸出:
這次第一順位的Hardwood並未實作出show_breed(),而第二順位的Conifer有實作,所以呼叫Conifer
的show_breed()
。這個結果應該很好理解。
情境-3:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
def show_breed(self): # 這次有自己的show_breed()。
print(f'Timba: {self.breed = :12}')
tree = Timba('timba')
tree.show_breed()
輸出:
這次Timba
實作屬於它自己了show_breed()
,結果當然是用自己的。
再說一次繼承體系「方法」呼叫的優先順序:
情境-4:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
def show_breed(self): # 這次有自己的show_breed()。
print(f'Timba: {self.breed = :12}')
tree = Timba('timba')
tree.show_breed()
Tree.show_breed(tree) # 直接呼叫Tree的show_breed(),注意這時必須「手動傳入」物件tree。
Hardwood.show_breed(tree) # 直接呼叫Hardwood的show_breed()。
Conifer.show_breed(tree) # 直接呼叫Conifer的show_breed()。
輸出:
本情境展示在繼承體系中,(主程式)跳過優先順序原則,直接呼叫父或祖父的方法。注意這時必須「手動傳入」物件(Tree.show_breed(tree
))。
情境-5:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
def show_breed(self): # 這次有自己的show_breed()。
print(f'Timba: {self.breed = :12}')
Tree.show_breed(self) # 在這裡呼叫祖父的show_breed(),傳入self。
Hardwood.show_breed(self) # 呼叫Hardwood的show_breed()。
Conifer.show_breed(self) # 呼叫Conifer的show_breed()。
tree = Timba('timba')
tree.show_breed()
也可以在Timba
類別的方法內呼叫其父執輩的方法。這次輸出和情境-4完全一樣:
情境-6:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
Tree.show_breed(self) # 呼叫其父類別的show_breed()。
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
Tree.show_breed(self) # 呼叫其父類別的show_breed()。
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
def show_breed(self): # 這次有自己的show_breed()。
print(f'Timba: {self.breed = :12}')
Hardwood.show_breed(self)
Conifer.show_breed(self)
tree = Timba('timba')
tree.show_breed()
輸出:
這回改在Hardwood
和Conifer
中以Tree.show_breed(self)
呼叫。
各位有沒注意到,以上的輸出,Tree.show_breed()
實際上跑了兩次(太過細節恕不追蹤)。有沒有辦法讓它只跑一次呢?有的,答案就是下面的情境:
情境-7:
class Tree():
def __init__(self, breed: str):
self.breed = breed
def show_breed(self):
print(f'Tree: {self.breed = :12}')
class Hardwood(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Hardwood: {self.breed = :12}')
super().show_breed() # 用super()來呼叫其父類別,這時不必傳入self。
class Conifer(Tree): # 繼承自Tree。
def show_breed(self): # 沒有自己的constructor。
print(f'Conifer: {self.breed = :12}')
super().show_breed() # 用super()來呼叫其父類別,這時不必傳入self。
class Timba(Hardwood, Conifer): # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
def show_breed(self): # 這次有自己的show_breed()。
print(f'Timba: {self.breed = :12}')
super().show_breed() # 用super()來呼叫其父類別,這時不必傳入self。
tree = Timba('timba')
tree.show_breed()
輸出:
還記得之前介紹過的super()
函數嗎?這次又派上用場了。
改用super().show_breed()
呼叫其父類別之後,Tree.show_breed()
竟然只跑一次。而且show_breed()方法還不必傳入self,較為方便好用。