python中繼承和多態

来源:https://www.cnblogs.com/xiaoyafei/archive/2018/05/28/9102734.html
-Advertisement-
Play Games

繼承和多態 繼承 引入繼承 我們有這樣一個需求 那我們實現的代碼是這樣的 我們仔細看代碼可以發現,在兩個類中有很多的重覆代碼,初始化方法和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中的+號,為什麼這麼說呢?+號不考慮直接使用的類型,就是說它不用考慮兩邊都是字元串才能用+號,或者兩邊都是數字才能用+號

車子有很多形態,學開車的時候沒有說奧迪怎麼開,寶馬怎麼開,只是學的是一套

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python代碼如下: ...
  • Java類庫中有為滿足不同需求而設計的不同的器,實際上就是不同的介面。最近學習了比較器、迭代器和文件過濾器這三個介面,我根據自己的理解做了一個不成熟的總結,假如有很多不准確甚至是錯誤的地方,希望大家多多賜教! 這三個介面在設計的時候,並不是只是聲明一個介面以及它裡面的方法,也在需要特定類“配合”這些 ...
  • 書名:流暢的Python作者:[巴西] Luciano Ramalho譯者:安道 吳珂ISBN:978-7-115-45415-7 需要學習的朋友可以通過網盤下載pdf版 http://tadown.com/fs/cyibbebnsahu08034/ 目標讀者本書的目標讀者是那些正在使用 Pytho ...
  • 在上一篇博客中已經介紹了django rest framework 對於認證的源碼流程,以及實現過程,當用戶經過認證之後下一步就是涉及到許可權的問題。比如訂單的業務只能VIP才能查看,所以這時候需要對許可權進行控制。下麵將介紹DRF的許可權控制源碼剖析。 這裡繼續使用之前的示例,加入相應的許可權,這裡先介紹 ...
  • 一個功能瀏覽器發送hello請求,伺服器接受請求並處理,響應Hello World字元串.1.創建一個maven工程;(jar)2.導入依賴Spring Boot相關的依賴 3.編寫一個主程式;啟動Spring Boot應用4.編寫相關Controller、Service 5.運行主程式測試6.簡化 ...
  • 前言 在 "上一篇" 文章中,回顧了Java的集合。而在本篇文章中主要介紹 多線程 的相關知識。主要介紹的知識點為線程的介紹、多線程的使用、以及在多線程中使用的一些方法。 線程和進程 線程 表示進程中負責程式執行的執行單元,依靠程式進行運行。線程是程式中的順序控制流,只能使用分配給程式的資源和環境。 ...
  • 本文主要記錄如何用input標簽和jquery實現多圖片的上傳和回顯,不會涉及後端的交互,大概的效果看圖 我們從零來做一個這樣的demo 第一步: 我們先完善一下我們的頁面,預設的input file標簽非常醜,我們美化一下它,不會的可以百度一下,就是外面套個盒子,設置input的opacity為0 ...
  • urlencode與urldecode 當url中包含中文或者參數包含中文,需要對中文或者特殊字元(/、&)做編碼轉換。 urlencode的本質:把字元串轉為gbk編碼,再把\x替換成%。如果終端是utf8編碼的,需要把結果再轉成utf8輸出,否則會亂碼。 urlencode urllib庫裡面的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...