物件導向之繼承

北京-IT拾荒者發表於2018-05-24

初識繼承 什麼是繼承

繼承是一種建立新類的方式,新建的類可以繼承一個或多個父類(python支援多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。

子類會“”遺傳”父類的屬性,從而解決程式碼重用問題(比如練習7中Garen與Riven類有很多冗餘的程式碼)

python中類的繼承分為:單繼承和多繼承

class ParentClass1: #定義父類
   pass

class ParentClass2: #定義父類
   pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
   pass

class SubClass2(ParentClass1,ParentClass2): #python支援多繼承,用逗號分隔開多個繼承的類
   pass
複製程式碼

檢視繼承

SubClass1.__bases__ #__base__只檢視從左到右繼承的第一個子類,__bases__則是檢視所有繼承的父類
(<class '__main__.ParentClass1'>,)
SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
複製程式碼

提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。

ParentClass1.__bases__
(<class 'object'>,)
ParentClass2.__bases__
(<class 'object'>,)
複製程式碼

繼承與抽象(先抽象再繼承) 繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承

抽象即抽取類似或者說比較像的部分。

抽象分成兩個層次:

1.將奧巴馬和梅西這倆物件比較像的部分抽取成類;

2.將人,豬,狗這三個類比較像的部分抽取成父類。

抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)

物件導向之繼承
繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

物件導向之繼承
繼承與重用性 使用繼承來重用程式碼比較好的例子

==========================第一部分
例如
&emsp;&emsp;貓可以:喵喵叫、吃、喝、拉、撒
&emsp;&emsp;狗可以:汪汪叫、吃、喝、拉、撒
如果我們要分別為貓和狗建立一個類,那麼就需要為 貓 和 狗 實現他們所有的功能,虛擬碼如下:
#貓和狗有大量相同的內容
class 貓:
   def 喵喵叫(self):
       print '喵喵叫'
   def 吃(self):
       # do something
   def 喝(self):
       # do something
   def 拉(self):
       # do something
   def 撒(self):
       # do something
class 狗:
   def 汪汪叫(self):
       print '喵喵叫'
   def 吃(self):
       # do something
   def 喝(self):
       # do something
   def 拉(self):
       # do something
   def 撒(self):
       # do something
==========================第二部分
上述程式碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:
&emsp;&emsp;動物:吃、喝、拉、撒
&emsp;&emsp;   貓:喵喵叫(貓繼承動物的功能)
&emsp;&emsp;   狗:汪汪叫(狗繼承動物的功能)
虛擬碼如下:
class 動物:
   def 吃(self):
       # do something
   def 喝(self):
       # do something
   def 拉(self):
       # do something
   def 撒(self):
       # do something
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 貓(動物):
   def 喵喵叫(self):
       print '喵喵叫'     
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 狗(動物):
   def 汪汪叫(self):
       print '喵喵叫'
==========================第三部分
#繼承的程式碼實現
class Animal:
   def eat(self):
       print("%s 吃 " %self.name)
   def drink(self):
       print ("%s 喝 " %self.name)
   def shit(self):
       print ("%s 拉 " %self.name)
   def pee(self):
       print ("%s 撒 " %self.name)
class Cat(Animal):
   def __init__(self, name):
       self.name = name
       self.breed = '貓'
   def cry(self):
       print('喵喵叫')
class Dog(Animal):
   def __init__(self, name):
       self.name = name
       self.breed='狗'
   def cry(self):
       print('汪汪叫')
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
複製程式碼

在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時

我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。

通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函式屬性),實現程式碼重用

class Hero:
   def __init__(self,nickname,aggressivity,life_value):
       self.nickname=nickname
       self.aggressivity=aggressivity
       self.life_value=life_value
   def move_forward(self):
       print('%s move forward' %self.nickname)
   def move_backward(self):
       print('%s move backward' %self.nickname)
   def move_left(self):
       print('%s move forward' %self.nickname)
   def move_right(self):
       print('%s move forward' %self.nickname)
   def attack(self,enemy):
       enemy.life_value-=self.aggressivity
class Garen(Hero):
   pass
class Riven(Hero):
   pass
g1=Garen('草叢倫',100,300)
r1=Riven('銳雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

'''
執行結果
'''
複製程式碼

提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就是大大縮短了軟體開發週期,對大型軟體開發來說,意義重大.

注意:像g1.life_value之類的屬性引用,會先從例項中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。

重點!!!:再看屬性查詢

class Foo:
   def f1(self):
       print('Foo.f1')
   def f2(self):
       print('Foo.f2')
       self.f1()
class Bar(Foo):
   def f1(self):
       print('Foo.f1')

b=Bar()
b.f2()
複製程式碼

派生 當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。

class Riven(Hero):
   camp='Noxus'
   def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
       print('from riven')
   def fly(self): #在自己這裡定義新的
       print('%s is flying' %self.nickname)
複製程式碼

在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值

class Riven(Hero):
   camp='Noxus'
   def __init__(self,nickname,aggressivity,life_value,skin):
       Hero.__init__(self,nickname,aggressivity,life_value) #呼叫父類功能
       self.skin=skin #新屬性
   def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
       Hero.attack(self,enemy) #呼叫功能
       print('from riven')
   def fly(self): #在自己這裡定義新的
       print('%s is flying' %self.nickname)
r1=Riven('銳雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)

'''
執行結果
銳雯雯 is flying
比基尼

'''
複製程式碼

組合與重用性 軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合

組合指的是,在一個類中以另外一個類的物件作為資料屬性,稱為類的組合

class Equip: #武器裝備類
   def fire(self):
print('release Fire skill')

class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類
camp='Noxus'
   def __init__(self,nickname):
   self.nickname=nickname
   self.equip=Equip() #用Equip類產生一個裝備,賦值給例項的equip屬性
r1=Riven('銳雯雯')
r1.equip.fire() #可以使用組合的類產生的物件所持有的方法
release Fire skill
複製程式碼

組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,

1.繼承的方式

通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。

當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人

2.組合的方式

用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...

例子:繼承與組合

class People:
   def __init__(self,name,age,sex):
       self.name=name
       self.age=age
       self.sex=sex
class Course:
   def __init__(self,name,period,price):
       self.name=name
       self.period=period
       self.price=price
   def tell_info(self):
       print('<%s %s %s>' %(self.name,self.period,self.price))
class Teacher(People):
   def __init__(self,name,age,sex,job_title):
       People.__init__(self,name,age,sex)
       self.job_title=job_title
       self.course=[]
       self.students=[]
class Student(People):
   def __init__(self,name,age,sex):
       People.__init__(self,name,age,sex)
       self.course=[]
egon=Teacher('egon',18,'male','沙河霸道金牌講師')
s1=Student('牛榴彈',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#為老師egon和學生s1新增課程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#為老師egon新增學生s1
egon.students.append(s1)
#使用
for obj in egon.course:
   obj.tell_info()
複製程式碼

當類之間有顯著不同,並且較小的類是較大的類所需要的元件時,用組合比較好

介面與歸一化設計 什麼是介面

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();
   } 
}
複製程式碼

PS:hi boy,給我開個查詢介面。。。此時的介面指的是:自己提供給使用者來呼叫自己功能的方式\方法\入口

為何要用介面

介面提取了一群類共同的函式,可以把介面當做一個函式的集合。

然後讓子類去實現介面中的函式。

這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個介面實現的類,那麼所有的這些類產生的物件在使用時,從用法上來說都一樣。

歸一化的好處在於:

  1. 歸一化讓使用者無需關心物件的類是什麼,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度。

  2. 歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的物件集合

2.1:就好象linux的泛檔案概念一樣,所有東西都可以當檔案處理,不必關心它是記憶體、磁碟、網路還是螢幕(當然,對底層設計者,當然也可以區分出“字元裝置”和“塊裝置”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。

2.2:再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函式呼叫)都一樣

模仿interface

在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿介面的概念

可以藉助第三方模組:

http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py裡使用zope.interface
文件https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
複製程式碼

也可以使用繼承:

繼承的兩種用途

一:繼承基類的方法,並且做出自己的改變或者擴充套件(程式碼重用):實踐中,繼承的這種用途意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。

二:宣告某個子類相容於某基類,定義一個介面類(模仿java的Interface),介面類中定義了一些介面名(就是函式名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能

class Interface:#定義介面Interface類來模仿介面的概念,python中壓根就沒有interface關鍵字來定義一個介面。
   def read(self): #定介面函式read
       pass
   def write(self): #定義介面函式write
       pass
class Txt(Interface): #文字,具體實現read和write
   def read(self):
       print('文字資料的讀取方法')
   def write(self):
       print('文字資料的讀取方法')
class Sata(Interface): #磁碟,具體實現read和write
   def read(self):
       print('硬碟資料的讀取方法')
   def write(self):
       print('硬碟資料的讀取方法')
class Process(Interface):
   def read(self):
       print('程式資料的讀取方法')
   def write(self):
       print('程式資料的讀取方法')
複製程式碼

上面的程式碼只是看起來像介面,其實並沒有起到介面的作用,子類完全可以不用去實現介面 ,這就用到了抽象類

抽象類 什麼是抽象類

與java一樣,python也有抽象類的概念但是同樣需要藉助模組實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被例項化

為什麼要有抽象類

如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。

  比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。

從設計角度去看,如果類是從現實物件抽象而來的,那麼抽象類就是基於類抽象而來的。
複製程式碼

  從實現角度來看,抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被例項化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,即將揭曉答案

在python中實現抽象類

#_*_coding:utf-8_*_
#一切皆檔案
import abc #利用abc模組實現抽象類

class All_file(metaclass=abc.ABCMeta):
   all_type='file'
   @abc.abstractmethod #定義抽象方法,無需實現功能
   def read(self):
       '子類必須定義讀功能'
       pass

   @abc.abstractmethod #定義抽象方法,無需實現功能
   def write(self):
       '子類必須定義寫功能'
       pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
   def read(self):
       print('文字資料的讀取方法')

   def write(self):
       print('文字資料的讀取方法')

class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
   def read(self):
       print('硬碟資料的讀取方法')

   def write(self):
       print('硬碟資料的讀取方法')

class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
   def read(self):
       print('程式資料的讀取方法')

   def write(self):
       print('程式資料的讀取方法')

wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆檔案的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
複製程式碼

抽象類與介面

抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函式屬性(如read、write),而介面只強調函式屬性的相似性。

抽象類是一個介於類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實現歸一化設計

繼承實現的原理 繼承順序

在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如A(B,C,D)

如果繼承關係為非菱形結構,則會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條分支的順序直到找到我們想要的屬性

如果繼承關係為菱形結構,那麼屬性的查詢方式有兩種,分別是:深度優先和廣度優先

物件導向之繼承

物件導向之繼承

class A(object):
   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()
print(F.__mro__) #只有新式才有這個屬性可以檢視線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類
複製程式碼

繼承原理(python如何實現的繼承)

python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如

F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, 
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, 
<class 'object'>]
複製程式碼

為了實現繼承,python會在MRO列表上從左到右開始查詢基類,直到找到第一個匹配這個屬性的類為止。 而這個MRO列表的構造是通過一個C3線性化演算法來實現的。我們不去深究這個演算法的數學原理,它實際上就是合併所有父類的MRO列表並遵循如下三條準則: 1.子類會先於父類被檢查 2.多個父類會根據它們在列表中的順序被檢查

3.如果對下一個類存在兩個合法的選擇,選擇第一個父類

子類中呼叫父類的方法 方法一:指名道姓,即父類名.父類方法()

#_*_coding:utf-8_*_
class Vehicle: #定義交通工具類
    Country='China'
    def __init__(self,name,speed,load,power):
        self.name=name
        self.speed=speed
        self.load=load
        self.power=power
    def run(self):
        print('開動啦...')
class Subway(Vehicle): #地鐵
   def __init__(self,name,speed,load,power,line):
       Vehicle.__init__(self,name,speed,load,power)
       self.line=line
   def run(self):
       print('地鐵%s號線歡迎您' %self.line)
       Vehicle.run(self)
line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
複製程式碼

方法二:super()

class Vehicle: #定義交通工具類
    Country='China'
    def __init__(self,name,speed,load,power):
        self.name=name
        self.speed=speed
        self.load=load
        self.power=power

    def run(self):
        print('開動啦...')

class Subway(Vehicle): #地鐵
   def __init__(self,name,speed,load,power,line):
       #super(Subway,self) 就相當於例項本身 在python3中super()等同於super(Subway,self)
       super().__init__(name,speed,load,power)
       self.line=line

   def run(self):
       print('地鐵%s號線歡迎您' %self.line)
       super(Subway,self).run()

class Mobike(Vehicle):#摩拜單車
   pass

line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
複製程式碼

強調:二者使用哪一種都可以,但最好不要混合使用

物件導向之繼承

識別圖中二維碼,歡迎關注python寶典

相關文章