繼承和多態 繼承 引入繼承 我們有這樣一個需求 那我們實現的代碼是這樣的 我們仔細看代碼可以發現,在兩個類中有很多的重覆代碼,初始化方法和attack方法,那麼我們學習面向對象就是來較少代碼重覆量的,有沒有辦法解決呢?答案是肯定有的 初識繼承 繼承,指的是 類與類之間的關係 ,是一種 xx是xx的關 ...
繼承和多態
繼承
引入繼承
我們有這樣一個需求
模仿英雄聯盟定義兩個英雄類
1.英雄要有昵稱、攻擊力、生命值屬性
2.實例化出兩個英雄對象
3.英雄之間可以互毆,被毆打的一方掉血,血量小於0則判斷為死亡
那我們實現的代碼是這樣的
class Gailun:
camp = 'demaxiya' # 定義英雄陣營
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
class Ruiwen:
camp = 'aioniya'
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
g1 = Gailun('蓋倫',100,50) # 實例化對象 # 昵稱:蓋倫、生命值:100、攻擊力:50
r1 = Ruiwen('瑞文',50,100)
print('瑞文原來生命值:',r1.life_value)
g1.attack(r1)
print('瑞文現在生命值:',r1.life_value)
# 運行結果為
瑞文原來生命值: 50
瑞文現在生命值: 0
我們仔細看代碼可以發現,在兩個類中有很多的重覆代碼,初始化方法和attack方法,那麼我們學習面向對象就是來較少代碼重覆量的,有沒有辦法解決呢?答案是肯定有的
初識繼承
繼承,指的是類與類之間的關係,是一種xx是xx的關係,繼承的功能之一就是用來解決代碼重用問題
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可以稱為基類或超類,新建的類稱為派生類或子類
讓我們繼續看下剛剛的那個例子,假設沒有英雄陣營(等下會講),Gailun類和Ruiwen類都是英雄,他們都有昵稱、生命值、攻擊力屬性,都有attack攻擊方法,所以...Gailun類和Ruiwen類能不能都屬於Hero類呢?
class Hero: # 定義一個Hero英雄類----父類
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定義類時在類名後面()寫上Hero,則代表繼承Hero類----子類
pass
class Ruiwen(Hero): # ----子類
pass
g1 = Gailun('蓋倫',100,50) # 昵稱:蓋倫、生命值:100、攻擊力:50
r1 = Ruiwen('瑞文',50,100)
print('瑞文原來生命值:',r1.life_value)
g1.attack(r1)
print('瑞文現在生命值:',r1.life_value)
# 運行結果如下:
瑞文原來生命值: 50
瑞文現在生命值: 0
在這段代碼中,Gailun類和Ruiwen類全部都是繼承了Hero類,所有的屬性和方法都是通過繼承得到的,所以我們剛剛說繼承就是xx是xx的關閉,比如Gailun是英雄、Ruiwen也是英雄,那麼我們就可以通過抽象他們直接相似的地方,總結類之間相似的特征得到父類,則父類中的東西子類全部繼承,這樣就解決了代碼重用問題
單繼承和多繼承
上面我們說過,在python中是支持單繼承和多繼承的,讓我們寫一段偽代碼來看看:
class ParentsClass1: # ----父類1
pass
class ParentsClass2: # ----父類2
pass
# 1.繼承一個父類
class SubClass1(ParentsClass1): # ----繼承父類1
pass
# 2.繼承多個父類
class SubClass2(ParentsClass1,ParentsClass2): # ----繼承父類1和父類2
pass
我們該如何查看繼承呢?
print(SubClass1.__bases__)
print(SubClass2.__bases__)
# 運行結果為
(<class '__main__.ParentsClass1'>,) # SubClass1繼承ParentsClass1
(<class '__main__.ParentsClass1'>, <class '__main__.ParentsClass2'>)
繼承中的屬性查找
實例化出一個對象後,訪問這個對象的屬性是如何查找的呢?首先會從對象本身開始查找,如果沒有的話,就去類裡面找,如果還沒有的話,就去父類中,父類之上可能還會有父類, 找不到才會報錯
# 屬性查找小練習
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('From Foo.f2')
class Bar(Foo):
def f2(self):
print('From Bar.f2')
b = Bar()
print(b.__dict__) # 這句話代表去這個對象b裡面去查找屬性,但是這個對象中並沒有__init__方法,所以就沒有任何屬性,列印結果為空
b.f2()
# 運行結果:
{}
From Bar.f2
# 小練習的修改版
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('From Foo.f2')
class Bar(Foo):
def f1(self):
print('From Bar.f1')
b = Bar()
b.f2() # 在對象和類中都沒有找到這個方法,所以會去類裡面找
# 運行結果:
From Foo.f2
派生類
在剛剛的Gailun和Ruiwen中,我們剛剛假設了沒有英雄陣營,在理論上,子類不應該一無所有,子類應該有自己的屬性
class Hero: # 定義一個Hero英雄類----父類
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定義類時在類名後面()寫上Hero,則代表繼承Hero類----子類
camp = 'demaxiya'
class Ruiwen(Hero): # ----子類
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50) # 昵稱:蓋倫、生命值:100、攻擊力:50
r1 = Ruiwen('瑞文',50,100)
print('蓋倫來自於:',g1.camp)
print('瑞文來自於:',r1.camp)
# 這樣,就把自己的屬性給加上去了,看看運行結果
蓋倫來自於: demaxiya
瑞文來自於: aioniya
重寫父類的方法
由於子類繼承了父類,所以會把父類的方法和屬性全都繼承,但是往往繼承下來的方法並不是我們想要的,所以這個時候就需要重寫父類的方法
class Hero: # 定義一個Hero英雄類----父類
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定義類時在類名後面()寫上Hero,則代表繼承Hero類----子類
camp = 'demaxiya'
def attack(self,enemy): # 重寫了父類中的attack方法
print('This is Gailun Class')
class Ruiwen(Hero): # ----子類
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50) # 昵稱:蓋倫、生命值:100、攻擊力:50
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
# 運行結果
This is Gailun Class
# 因為我們一直在說屬性查找,屬性是從當前對象開始查找,因為繼承了Hero類,所以有nickname,life_value,aggrensivity屬性,然後去類裡面找,因為是重寫父類的方法, 所以在類中找到了attack方法,就不用再去管父類中的attack方法了
繼承實現的原理
python到底是如何實現繼承的呢?對於你每定義的一個類,python會計算出一個方法解析順序MRO列表,這個mro列表就是一個簡單的所有基類的線性順序列表,比如:
class Hero: # 定義一個Hero英雄類----父類
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵稱
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻擊力
def attack(self,enemy): # 攻擊方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定義類時在類名後面()寫上Hero,則代表繼承Hero類----子類
camp = 'demaxiya'
def attack(self,enemy):
print('This is Gailun Class')
class Ruiwen(Hero): # ----子類
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50) # 昵稱:蓋倫、生命值:100、攻擊力:50
r1 = Ruiwen('瑞文',50,100)
print(Gailun.mro())
# 列印結果為
[<class '__main__.Gailun'>, <class '__main__.Hero'>, <class 'object'>]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,知道找到第一個匹配這個屬性的類為止。而這個MRO列表的構造是通過一個C3線性化演算法來實現的,我們不去研究這個演算法的數字原理,它實際上就是合併所有父類的MRO列表並遵循如下三條準則:
- 子類會先於父類被檢查(對象比類先檢查)
- 多個父類會根據它們在列表中的數據被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
在前面部分我們說到,python既可以支持單繼承也可以支持多繼承,如果子類繼承了多個父類,那麼屬性的查找方式就有兩種:深度優先和廣度優先
在講深度優先和廣度優先之前,我們先講一下新式類和經典類
在python中分為兩種類,但是這個概念只是在python2中才會有,在python3中都是新式類。
在python2中,經典類:沒有繼承object的類,以及它的子類都稱為經典類
class Parent:
pass
class Child(Parent):
pass
在python2中,新式類:繼承object的類,以及它的子類都稱為新式類
class Parent1(object):
pass
class Child1(Parent1):
pass
但是在python3中,沒有了經典類和新式類的區分,全部都是新式類,不管有沒有繼承object
class Foo:
pass
class Bar(Foo):
pass
# 或者是
class Foo(object):
pass
class Bar(Foo):
pass
怎麼會講到新式類和經典類呢? 因為新式類的查找順序是廣度優先,而經典類的查找順序是深度優先
- 深度優先:
- 廣度優先:
舉例代碼
class A:
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test() # 先找到test屬性,然後發現test是一個方法,加上括弧就可以運行了
print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性
# 運行結果
from D # 這裡可以知道,繼承多個父類,是從第一個父類裡面進去查找方法的
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
但是這段代碼在python2中運行結果是不一樣的,因為python2中父類沒有繼承object,那麼它和它的子類都被稱為經典類
import inspect
class A:
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test() # 先找到test屬性,然後發現test是一個方法,加上括弧就可以運行了
print(inspect.getmro(F))
# 運行結果如下
self D
(<class __main__.F at 0x7fdcf68dea78>, <class __main__.D at 0x7fdcf68de9a8>, <class __main__.B at 0x7fdcf68de8d8>, <class __main__.A at 0x7fdcf68de870>, <class __main__.E at 0x7fdcf68dea10>, <class __main__.C at 0x7fdcf68de940>)
這樣,我們就看出來深度優先和廣度優先的區別了
在子類中調用父類的方法
在子類派生出的新方法中,往往需要重用父類的方法,例如:在父類的初始化方法中,有name,age這兩個屬性,作為子類,我們繼承了父類,有著和父類一樣的name,age,但是子類中還想有一個addr屬性,這個要如何添加呢?我們用之前的例子舉例:
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50)
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
print(r1.life_value)
這段代碼我們已經完成了,那麼我們看一下這個,在父類中有著初始化方法,在子類中同樣也有這個方法,那麼我問你,繼承的作用是什麼?繼承是解決類與類之間的關係,減少代碼重用的問題,但是在這裡代碼很明顯重覆了,需要如何修改呢?
第一種方法:指名道姓(不依賴繼承)
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
def attack(self,enemy): # 子類重寫父類方法
Hero.attack(self,enemy) # 指名道姓的方法,告訴Hero,這個類的attak我要調用
print('這是Gailun Class')
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50)
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
print('瑞文現在生命值:',r1.life_value)
# 運行結果為:
這是Gailun Class
0
第二種方法:super方法(依賴繼承)
# 要給蓋倫添加一個武器屬性
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
def __init__(self,nickname,life_value,aggrensivity,weapon):
super().__init__(nickname,life_value,aggrensivity)
self.weapon = weapon
def attack(self,enemy):
Hero.attack(self,enemy)
print('這是Gailun Class')
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('蓋倫',100,50,'金箍棒')
r1 = Ruiwen('瑞文',50,100)
print(g1.__dict__)
g1.attack(r1)
print('瑞文現在生命值:',r1.life_value)
# 運行結果為
{'nickname': '蓋倫', 'life_value': 100, 'aggrensivity': 50, 'weapon': '金箍棒'}
這是Gailun Class
瑞文現在生命值: 0
這兩種方式的區別是:方式一是和繼承沒有關係的,而方法二是依賴於繼承的,並且即使沒有直接繼承關係,super仍然會按照MRO列表繼續往後查找
super()是如何依賴於繼承呢?只要有子類繼承了多個父類,那麼python就會計算出MRO列表,而super就是在MRO列表中去找屬性
舉例說明
class A:
def test(self):
print('from A.test')
super().test()
class B:
def test(self):
print('from B.test')
class C(A,B):
pass
print(C.__mro__) # 只是列印C的MRO列表,即屬性的查找順序
# 運行結果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
我們現在實例化一個對象看看:
c = C()
c.test()
# 運行結果
from A.test
from B.test
那如果沒有加上super方法呢?
class A:
def test(self):
print('from A.test')
class B:
def test(self):
print('from B.test')
class C(A,B):
pass
c = C()
c.test()
# 運行結果如下:只會列印一個,因為對於這個對象而言,已經找到了test屬性了
from A.test
組合
在剛剛講到繼承的時候,我們說過:繼承就是解決類與類之間的關係,它是xx是xx的關係,例如:瑞文是英雄,德瑪西亞是英雄這些,使用繼承的好處就是可以重用代碼,那麼組合是什麼呢?就是xx有xx類的關係,例如:老師有課程類,學生有課程類,把老師類和課程類組合在一起就叫做組合,通過組合,可以把很多類組合在一起
用組合的方式建立了類與組合的類之間的關係,它是一種'有'的關係,比如:教授有生日,教授教雲計算基礎和linux基礎,教授有學生s1,s2,s3
class People: # 定義父類----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定義子類Teacher繼承父類People,但是老師要有等級,要有薪水,就要用到上面的重用父類的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
class Student(People): # 定義子類Student繼承父類People,學生要有上課時間
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
# 實例化對象
t1 = Teacher('方明清',35,'male',10,10000)
s1 = Student('肖亞飛',22,'male','08:30:00')
那麼老師有課程即老師有課程類如何組合的呢?
class People: # 定義父類----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定義子類Teacher繼承父類People,但是老師要有等級,要有薪水,就要用到上面的重用父類的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
def tell_info(self):
print('姓名:%s,年齡:%s,性別:%s,等級:%s,薪水:%s'%(self.name,self.age,self.sex,self.level,self.salary))
class Student(People): # 定義子類Student繼承父類People,學生要有上課時間
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('課程:<%s>\t\t價格:<%s>\t\t學習周期:<%s>'%(self.course_name,self.course_price,self.course_period))
# 實例化對象
t1 = Teacher('方明清',35,'male',10,10000)
t2 = Teacher('李曉明',38,'female',8,8000)
s1 = Student('肖亞飛',22,'male','08:30:00')
# 添加課程對象
c1 = Course('雲計算基礎',300,'5mouths')
c2 = Course('Linux基礎',800,'3mouths')
# 把老師類和課程類組合,老師有課程
t1.course = c1
t2.course = c2
# 列印t1和t2老師的信息和課程
t1.tell_info()
t1.course.tell_info()
print()
t2.tell_info()
t2.course.tell_info()
# 運行結果為
姓名:方明清,年齡:35,性別:male,等級:10,薪水:10000
課程:<雲計算基礎> 價格:<300> 學習周期:<5mouths>
姓名:李曉明,年齡:38,性別:female,等級:8,薪水:8000
課程:<Linux基礎> 價格:<800> 學習周期:<3mouths>
組合2舉例
class People: # 定義父類----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定義子類Teacher繼承父類People,但是老師要有等級,要有薪水,就要用到上面的重用父類的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
def tell_info(self):
print('姓名:%s,年齡:%s,性別:%s,等級:%s,薪水:%s'%(self.name,self.age,self.sex,self.level,self.salary))
class Student(People): # 定義子類Student繼承父類People,學生要有上課時間
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('課程:<%s>\t\t價格:<%s>\t\t學習周期:<%s>'%(self.course_name,self.course_price,self.course_period))
class data:
def __init__(self,year,mon,day):
self.year = year
self.mon = mon
self.day = day
def tell_info(self):
print('%s-%s-%s'%(self.year,self.mon,self.day))
# 實例化對象
t1 = Teacher('方明清',35,'male',10,10000)
t2 = Teacher('李曉明',38,'female',8,8000)
s1 = Student('肖亞飛',22,'male','08:30:00')
# 實例化生日對象
d1 = data('1996','4','30')
# 學生有生日f
s1.birthday = d1
s1.birthday.tell_info() # 列印s1學生的生日信息
# 運行結果為
1996-4-30
總結:
當類之間有顯著不同,並且較小的類是較大的類所需要的組建時,用組合比較好
抽象類與歸一化
hi boy,給我開個查詢介面。。。此時的介面指的是:自己提供給使用者來調用自己功能的方式\方法\入口,java中的interface使用如下
=================第一部分:Java 語言中的介面很好的展現了介面的含義: IAnimal.java
/*
* Java的Interface介面的特征:
* 1)是一組功能的集合,而不是一個功能
* 2)介面的功能用於交互,所有的功能都是public,即別的對象可操作
* 3)介面只定義函數,但不涉及函數實現
* 4)這些功能是相關的,都是動物相關的功能,但光合作用就不適宜放到IAnimal裡面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:豬”的類設計,實現了IAnnimal介面
package com.oo.demo;
public class Pig implements IAnimal{ //如下每個函數都需要詳細實現
public void eat(){
System.out.println("Pig like to eat grass");
}
public void run(){
System.out.println("Pig run: front legs, back legs");
}
public void sleep(){
System.out.println("Pig sleep 16 hours every day");
}
public void speak(){
System.out.println("Pig can not speak"); }
}
=================第三部分:Person2.java
/*
*實現了IAnimal的“人”,有幾點說明一下:
* 1)同樣都實現了IAnimal的介面,但“人”和“豬”的實現不一樣,為了避免太多代碼導致影響閱讀,這裡的代碼簡化成一行,但輸出的內容不一樣,實際項目中同一介面的同一功能點,不同的類實現完全不一樣
* 2)這裡同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應用場景下,具備的屬性和功能是完全不一樣的 */
package com.oo.demo;
public class Person2 implements IAnimal {
public void eat(){
System.out.println("Person like to eat meat");
}
public void run(){
System.out.println("Person run: left leg, right leg");
}
public void sleep(){
System.out.println("Person sleep 8 hours every dat");
}
public void speak(){
System.out.println("Hellow world, I am a person");
}
}
=================第四部分:Tester03.java
package com.oo.demo;
public class Tester03 {
public static void main(String[] args) {
System.out.println("===This is a person===");
IAnimal person = new Person2();
person.eat();
person.run();
person.sleep();
person.speak();
System.out.println("\n===This is a pig===");
IAnimal pig = new Pig();
pig.eat();
pig.run();
pig.sleep();
pig.speak();
}
}
# java中的interface
python中舉例說明抽象類和歸一化:
class People:
def run(self):
print('Prople is run')
class Dog:
def walk(self):
print('Dog is walk')
class Pig:
def zou(self):
print('Pig is zou')
# 實例化對象
p = People()
d = Dog()
p1 = Pig()
# 調用類的方法
p.run()
d.walk()
p1.zou()
這個是我簡單寫的代碼,實現了人、狗、豬走路的功能,這一共就幾行代碼相信很容易就能看懂了,那麼問題來了,如果我只讓你看這麼個代碼,請你告訴我代表著是什麼?
class People:...
class Dog:...
class Pig:...
# 實例化對象
p = People()
d = Dog()
p1 = Pig()
# 調用類的方法
p.run()
d.walk()
p1.zou()
請你現在告訴我p.run()代表著會實現什麼功能?沒有人會知道的,因為類裡面的方法被我隱藏了,好了,現在我們開始講一下歸一化
所謂的歸一化,就是只要是基於同一個介面實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都是一樣的
歸一化的好處
- 歸一化讓使用者無需關心對象的類是什麼,只需要知道的是這些對象都具有什麼功能就好了,這極大的降低了使用者的難度(人、豬、狗,它們都會走路,都會吃,那麼能不能定義一個統一的標準,人走路叫run,狗走路也叫run呢?)
- 歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的對象集合,在linux中,有這麼一句說法:一切皆文件,在linux中,所有的東西都可以當成文件來處理,你無需關係它是網路、記憶體、磁碟、還是屏幕;再比如:你去考取駕照,沒有規定你學完只能開寶馬或者奧迪,大家只需要拿到駕駛證,本田能不能開?可以,賓士能不能開?也可以,開的時候無需關心我開的是哪一輛車,操作手法都是一樣的。
抽象類
所謂的抽象類,就是把類與類之間相似的部分拿出來然後得到父類,然後讓子類去繼承父類,這樣做的好處就是:讓子類繼承父類的同時必須按照父類規定的方法來
抽象類本身就是一個類,屬性查找的順序就是:對象->類->父類->父類的父類...,所以在父類中定義屬性在所有的子類中就是公用的
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,即將揭曉答案
import abc
class Animal(metaclass=abc.ABCMeta):
all_type = 'animal'
@abc.abstractmethod
def run(self):
pass
class People(Animal):
def run(self):
print('Prople is run')
class Dog(Animal):
def walk(self):
print('Dog is walk')
class Pig(Animal):
def zou(self):
print('Pig is zou')
# 實例化對象
p = People()
dog1 = Dog()
# 調用類的方法
p.run()
dog1.walk()
# 運行結果
Traceback (most recent call last):
File "D:/py_study/day17-繼承開始/test.py", line 30, in <module>
dog1 = Dog()
TypeError: Can't instantiate abstract class Dog with abstract methods run
報錯......提示要使用run方法
修改為如下:
import abc
class Animal(metaclass=abc.ABCMeta):
all_type = 'animal'
@abc.abstractmethod
def run(self):
pass
class People(Animal):
def run(self):
print('Prople is run')
class Dog(Animal):
def run(self):
print('Dog is walk')
class Pig(Animal):
def run(self):
print('Pig is zou')
# 實例化對象
p = People()
dog1 = Dog()
pig1 = Pig()
# 調用類的方法
p.run()
dog1.run()
pig1.run()
這樣,對於使用者來說:不管你是人、狗、豬,都可以使用run方法,run方法都是走路
註意:
抽象類只能被繼承,而不能被實例化,它的功能就是規範子類並不是把父類定義出來直接用,而是間接用的
多態與多態性
多態
同一類事物的多種形態叫做多態,例如:動物有多種形態(人、狗、豬)
import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #動物的形態之一:人
def talk(self):
print('say hello')
class Dog(Animal): #動物的形態之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #動物的形態之三:豬
def talk(self):
print('say aoao')
文件有多種形態:文本文件和可執行文件
import abc
class File(metaclass=abc.ABCMeta): #同一類事物:文件
@abc.abstractmethod
def click(self):
pass
class Text(File): #文件的形態之一:文本文件
def click(self):
print('open file')
class ExeFile(File): #文件的形態之二:可執行文件
def click(self):
print('execute file')
多態性
多態性指的是在不考慮實例類型的情況下使用實例,多態性分為靜態多態性和動態多態性
動態多態性:如任何類型都可以用運算符+進行計算(等下講)
靜態多態性:如下
peo=People()
dog=Dog()
pig=Pig()
# peo、dog、pig都是動物,只要是動物那麼就按照動物的標準使用,就肯定有run方法
# 於是我們便可以不考慮他們三者具體是什麼類型,而直接使用
peo.run()
dog.run()
pig.run()
# 更近一步,我們可以定義一個統一的介面來使用,不用考慮對象的類型了
def func(obj):
obj.talk()
靜態多態性
其實我們之前已經接觸過了,就比如python中的+號,為什麼這麼說呢?+號不考慮直接使用的類型,就是說它不用考慮兩邊都是字元串才能用+號,或者兩邊都是數字才能用+號
車子有很多形態,學開車的時候沒有說奧迪怎麼開,寶馬怎麼開,只是學的是一套