python開發面向對象基礎:接口類&抽象類&多態&多繼承
一,接口類
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)
二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能
開發中容易出現的問題
1 class Alipay: 2 ‘‘‘ 3 支付寶支付 4 ‘‘‘ 5 def pay(self,money): 6 print(‘支付寶支付了%s元‘%money) 7 8 class Applepay: 9 ‘‘‘ 10 apple pay支付11 ‘‘‘ 12 def pay(self,money): 13 print(‘apple pay支付了%s元‘%money) 14 15 class Wechatpay: 16 def fuqian(self,money): 17 ‘‘‘ 18 實現了pay的功能,但是名字不一樣 19 ‘‘‘ 20 print(‘微信支付了%s元‘%money) 21 22 def pay(payment,money): 23 ‘‘‘ 24 支付函數,總體負責支付 25 對應支付的對象和要支付的金額26 ‘‘‘ 27 payment.pay(money) 28 29 30 p = Wechatpay() 31 pay(p,200) #執行會報錯
接口初成:手動報異常:NotImplementedError來解決開發中遇到的問題
借用abc模塊來實現接口
1 from abc import ABCMeta,abstractmethod 2 3 class Payment(metaclass=ABCMeta): 4 @abstractmethod 5 def pay(self,money): 6 pass 7 8 9class Wechatpay(Payment): 10 def fuqian(self,money): 11 print(‘微信支付了%s元‘%money) 12 13 p = Wechatpay() #不調就報錯了
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細致到什麽程度,視需求而定)。
依賴倒置原則: 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程
*****接口提取了一群類共同的函數,可以把接口當做一個函數的集合。 然後讓子類去實現接口中的函數。 這麽做的意義在於歸一化,什麽叫歸一化,就是只要是基於同一個接口實現的類,那麽所有的這些類產生的對象在使用時,從用法上來說都一樣。 歸一化,讓使用者無需關心對象的類是什麽,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
二,抽象類
什麽是抽象類
與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
為什麽要有抽象類
如果說類是從一堆對象中抽取相同的內容而來的,那麽抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麽是吃一個具體的香蕉,要麽是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麽抽象類就是基於類抽象而來的。
1 #一切皆文件 2 import abc #利用abc模塊實現抽象類 3 4 class All_file(metaclass=abc.ABCMeta): 5 all_type=‘file‘ 6 @abc.abstractmethod #定義抽象方法,無需實現功能 7 def read(self): 8 ‘子類必須定義讀功能‘ 9 pass 10 11 @abc.abstractmethod #定義抽象方法,無需實現功能 12 def write(self): 13 ‘子類必須定義寫功能‘ 14 pass 15 16 # class Txt(All_file): 17 # pass 18 # 19 # t1=Txt() #報錯,子類沒有定義抽象方法 20 21 class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法 22 def read(self): 23 print(‘文本數據的讀取方法‘) 24 25 def write(self): 26 print(‘文本數據的讀取方法‘) 27 28 class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法 29 def read(self): 30 print(‘硬盤數據的讀取方法‘) 31 32 def write(self): 33 print(‘硬盤數據的讀取方法‘) 34 35 class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法 36 def read(self): 37 print(‘進程數據的讀取方法‘) 38 39 def write(self): 40 print(‘進程數據的讀取方法‘) 41 42 wenbenwenjian=Txt() 43 44 yingpanwenjian=Sata() 45 46 jinchengwenjian=Process() 47 48 #這樣大家都是被歸一化了,也就是一切皆文件的思想 49 wenbenwenjian.read() 50 yingpanwenjian.write() 51 jinchengwenjian.read() 52 53 print(wenbenwenjian.all_type) 54 print(yingpanwenjian.all_type) 55 print(jinchengwenjian.all_type)
抽象類與接口類
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計
在python中,並沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念。
1.多繼承問題
在繼承抽象類的過程中,我們應該盡量避免多繼承;
而在繼承接口的時候,我們反而鼓勵你來多繼承接口
接口隔離原則: 使用多個專門的接口,而不使用單一的總接口。即客戶端不應該依賴那些不需要的接口。
2.方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能需要子類實現
三,鉆石繼承
1 class A(object): 2 def test(self): 3 print(‘from A‘) 4 5 class B(A): 6 def test(self): 7 print(‘from B‘) 8 9 class C(A): 10 def test(self): 11 print(‘from C‘) 12 13 class D(B): 14 def test(self): 15 print(‘from D‘) 16 17 class E(C): 18 def test(self): 19 print(‘from E‘) 20 21 class F(D,E): 22 # def test(self): 23 # print(‘from F‘) 24 pass 25 f1=F() 26 f1.test() 27 print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性 28 29 #新式類繼承順序:F->D->B->E->C->A 30 #經典類繼承順序:F->D->B->A->E->C 31 #python3中統一都是新式類 32 #pyhon2中才分新式類與經典類 33 34 繼承順序
繼承原理
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.如果對下一個類存在兩個合法的選擇,選擇第一個父類
四,多態
多態指的是一類事物有多種形態
動物有多種形態:人,狗,豬
1 import abc 2 class Animal(metaclass=abc.ABCMeta): #同一類事物:動物 3 @abc.abstractmethod 4 def talk(self): 5 pass 6 7 class People(Animal): #動物的形態之一:人 8 def talk(self): 9 print(‘say hello‘) 10 11 class Dog(Animal): #動物的形態之二:狗 12 def talk(self): 13 print(‘say wangwang‘) 14 15 class Pig(Animal): #動物的形態之三:豬 16 def talk(self): 17 print(‘say aoao‘)
文件有多種形態:文本文件,可執行文件
1 import abc 2 class File(metaclass=abc.ABCMeta): #同一類事物:文件 3 @abc.abstractmethod 4 def click(self): 5 pass 6 7 class Text(File): #文件的形態之一:文本文件 8 def click(self): 9 print(‘open file‘) 10 11 class ExeFile(File): #文件的形態之二:可執行文件 12 def click(self): 13 print(‘execute file‘)
多態性
一 什麽是多態動態綁定(在繼承的背景下使用時,有時也稱為多態性)
多態性是指在不考慮實例類型的情況下使用實例
在面向對象方法中一般是這樣表述多態性: 向不同的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會產生不同的行為(即方法)。 也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。 比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同
多態性分為靜態多態性和動態多態性
靜態多態性:如任何類型都可以用運算符+進行運算
動態多態性:如下
1 peo=People() 2 dog=Dog() 3 pig=Pig() 4 5 #peo、dog、pig都是動物,只要是動物肯定有talk方法 6 #於是我們可以不用考慮它們三者的具體是什麽類型,而直接使用 7 peo.talk() 8 dog.talk() 9 pig.talk() 10 11 #更進一步,我們可以定義一個統一的接口來使用 12 def func(obj): 13 obj.talk()
鴨子類型
逗比時刻:
Python崇尚鴨子類型,即‘如果看起來像、叫聲像而且走起路來像鴨子,那麽它就是鴨子’
python程序員通常根據這種行為來編寫程序。例如,如果想編寫現有對象的自定義版本,可以繼承該對象
也可以創建一個外觀和行為像,但與它無任何關系的全新對象,後者通常用於保存程序組件的松耦合度。
例1:利用標準庫中定義的各種‘與文件類似’的對象,盡管這些對象的工作方式像文件,但他們沒有繼承內置文件對象的方法
例2:序列類型有多種形態:字符串,列表,元組,但他們直接沒有直接的繼承關系
1 #二者都像鴨子,二者看起來都像文件,因而就可以當文件一樣去用 2 class TxtFile: 3 def read(self): 4 pass 5 6 def write(self): 7 pass 8 9 class DiskFile: 10 def read(self): 11 pass 12 def write(self): 13 pass 14 15 例子
#二者都像鴨子,二者看起來都像文件,因而就可以當文件一樣去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
python開發面向對象基礎:接口類&抽象類&多態&多繼承