2019年1月4日 星期五

python - 抽象類與接口類


抽象類與接口類

接口類

繼承有兩種用途:
一、繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)
二: 聲明某個子類兼容于某基類,定義一個接口類lnterface,接口類中定義了一些接口名(就是函數) 且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能

class Alipay:
    '''    信用卡支付       '''    def pay(self,money):
        print('信用卡支付了%s元'%money)

class Applepay:
    '''       apple pay支付          '''    def pay(self,money):
        print('apple pay支付了%s元'%money)

def pay(payment,money):
    '''' 支付函數 總體負責支付        
        對應支付的對象和要支付的金額 '''    
        payment.pay(money)

p = Alipay()
pay(p,200)
執行結果:
信用卡支付了200元

.開發中容易出現的錯誤
class Alipay:
    '''    信用卡支付       '''    def pay(self,money):
        print('信用卡支付了%s元'%money)

class googlepay:
    def fuqian(self,money):
    '''       實現了pay的功能,但是名子不一樣          '''    print('google pay支付了%s元'%money)

def pay(payment,money):
         '''' 支付函數 總體負責支付        
         對應支付的對象和要支付的金額  '''    
         payment.pay(money)

p = googlepay()
pay(p,200)    #執行會報錯
執行結果:
File "D:/python  t11/t10.py", line 13
    實現了pay的功能,但是名子不一樣
          '''
                                  ^
IndentationError: expected an indented block


.接口初成: 手動報異常 : NotlmplementedError 來解決開發中遇到的問題

class Payment:
    def pay(self):
        raise  NotImplementedError
class googlepay(Payment):
    def fuqian(self,money):
        print('Google 支付了%s元'%money)

p = googlepay() #不抱錯pay(p,200) #這裡錯誤
執行結果:
Traceback (most recent call last):
  File "D:/python  t11/t12.py", line 10, in <module>
    pay(p,200) #這裡錯誤
NameError: name 'pay' is not defined

.借用 abc 模塊 來實現接口
from abc import ABCMeta ,abstractclassmethod

class Payment(metaclass = ABCMeta):
    @ abstractclassmethod    def pay (self,money):
        pass
class googlepay(Payment):
    def fuqian(self,money):
        print('Google pay支付了%s元' %money)

p = googlepay() #不調用 就抱錯了!
執行結果:
Traceback (most recent call last):
  File "D:/python  t11/t13.py", line 13, in <module>
    p = googlepay() #不調用 就抱錯了!
TypeError: Can't instantiate abstract class googlepay with abstract methods pay

實踐中,繼承的第一種涵義意義並不很大,甚至常常是有害的。 因為它使得子類與基類出現強耦合。

繼承的第二種涵義非常重要。 它又叫做"接口繼承"。
接口繼承 實質上是要求"做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無須關心具體細節,可一視同仁地處裡實現了特定接口的所有對象"   ----這在程序設計上,叫做 歸一化。

歸一化 使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合 ---  就好像linux 的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存 、 磁盤、 網路 還是螢幕(當然 ,對底層設計者,當然也可以區分出"字符設備"和 "快設備" .然後最初針對性的設計: 細緻到甚麼程度,視需求而定)。


依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節; 細節應該依賴抽象。  換言之,要針對接口編程,而不適針對實現編程。


在python中根本就沒有一個叫做  interface 的關鍵字,上面的代碼只是看起來像接口,其實並沒有起到接口的作用, 子類完全可以不用去實現接口, 如果非要去模仿接口的概念,可以借助第三方模塊:

http://pypi.python.org/pypi/zope.interface

twisted的 ttwisted\internet\interface.py 裡面使用 zope.interface

文檔 : https://zopeinterface.readthedocs.io/en/latest/

設計模式 :  https://github.com/faif/python-patterns



.為何要使用接口 ?
接口提取了一群類共同的函數,可以把接口當作一個函數的集合。
然後讓子類去現實街口中的函數。
這麼做的意義在於歸一化,甚麼叫歸一化,就是只要基於同一個接口實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心對象的類是甚麼,只需要的知道這些對象都具備某些功能就可以了!這極大的降低了使用者的使用難度。
例如: 我們定義一個動物接口,接口裡定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松樹的類也去實現了該接口,由二者分別生產一隻老鼠跟一隻松鼠送到你的面前,就算你分別不清楚老鼠跟松鼠的能力,看你可以知道他們都會跑、也會吃、呼吸。

再舉一個例子:  我們有一個汽車接口,裡面定義了汽車所有功能,然後由本田汽車的類、奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,這樣就方便許多了!只要由這些車廠生產出來的車子都會是一樣的! 只要駕駛者學會駕駛就可以開這些汽車,不需要去擔心哪一個類(廠家)生產的,駕駛的方法(函數調用)都一樣的。

抽象類

什麼是抽象類???
與Java 一樣,pythonu 也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,他的特殊之處在於只能被繼承,不能被實例化。

為甚麼要有抽象類?
如果說類是從一堆 對象 中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬據和函數屬性。

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

從設計的角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。

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

.#在python 中實現抽象類
#一切皆文件import abc #利用abc 模塊實現抽象類
class All_file(metaclass=abc.ABCMeta):
    all_type = 'file'    @abc.abstractclassmethod #定義抽象方法,無須實現功能    def read(self):
        '子類必須定義讀功能'        pass
    @abc.abstractclassmethod #定義抽象方法,無須實現功能    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)
執行結果:
文本數據的讀取方法
硬碟數據的讀取方法
硬碟數據的讀取方法
file
file
file

抽象類與接口類


抽象類的本質還是類,指的是一組類的相似性 ,  包括數據屬性(如all_type) 和函數屬性和 函數屬性(如read,write) ,而接口只強調函數屬性的相似性。

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

在python 中,並沒有接口類這種東西,即便不通過專門的模塊定義接口類,我們也應該有一些基本的概念。

1.多繼承問題

在繼承抽象類的過程中,我們應該勁量避免多繼承;
而在繼承接口的時候, 我們反而鼓勵你來多繼承接口

.接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。 即客戶端不應該依賴那些不需要的接口。

2.方法的實現

在抽象類 中,我們可以對一些抽象方法做出基礎實現:
而在接口類中,任何方法都只是一種規範,具體的功能需要子類實現

钻石继承

繼承順序

1.python的類可以繼承多個類,Java 和 C#中則只能繼承一個類
2.python 的類如果繼承了多個類,那麼其尋找方法的方式只有兩種,
分別是: 深度優先  跟 廣度優先
 ●當類是經典類時, 多繼承情況下,會按照深度優先方式查找
 ●當類是新式類時, 多繼承情況下,會按照深度優先方式查找

經典類 及 新式類,從字面上可以看出一個老一個新,新的必然包含了更多的功能,也就是之後推薦的寫法,從寫法上區分的話,如果 當前類 或者 父類 繼承了  object類 ,那麼該類便是新式類,否則便是經典類。
.繼承順序
class  A(object):
    def test(self):
        print('frome 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 E')    pass
f1 = F()
f1.test()
print(F.__mro__)
#只有新式才有這個屬性可以查看線性列表# 經典類沒有這屬性

#新式類繼承順序 : F->D->E->C->A#經典類繼承順序 : F->D->A->E->C#python3 中統一都是新式類#python2 中才分新式類與經典類
 .執行結果:
from D
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>,
 <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

繼承原理

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.多繼承問題
在繼承抽象類的過程中,我們應該盡量避免多繼承;
而在繼承接口的時候,我們反而果粒你來多繼承接口

2.方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能需要子類實現

鑽石繼承

新式類: 廣度優先
經典類: 深度優先


沒有留言:

張貼留言