如何使用Python基于接口編程的方法實現(xiàn)
軟件行業(yè),唯一不變的就是變化。產(chǎn)品經(jīng)理會變,產(chǎn)品需求會變,代碼同樣要跟著變。
不同的代碼設計,變化所帶來的工作量更是不同,有的每改一次需求,近乎一次重構,而有的只需要修改一個配置文件,或者在類里添加一行代碼。當然比較好的代碼設計,由于有著良好的可擴展性,高內(nèi)聚,低耦合,因而易維護, 以少變應萬變。如果才能有好的代碼設計,就需要我們學習設計模式。今天為你分享的是在Python中,如何基于接口編程。
1994 年 GoF 的《設計模式》一書中有一個重要的原則就是:基于接口而非實現(xiàn)編程,英文源文是「Program to an interface,not an implementaion」,這里的所說的 interface,并不是特定編程語言中的接口,它是語言無關的,是指開發(fā)者提供給使用者的一個功能列表,理解了這一點非常重要。接口在 java 語言中是有關鍵字 interface 來實現(xiàn)的,java 不支持類的多重繼承,但支持接口的多重繼承,所在 java 開發(fā)者對接口非常熟悉了,Python 其實完全不需要 Java 那樣的設計,但可以借鑒接口的優(yōu)點。
先通過一個實例來了解下接口到底解決什么問題。
比如你正在實現(xiàn)一個圖片上傳功能,目前采用七牛云來存儲,你的類可能是這樣的。
class QnyImageStore(object): def getToken(): pass def upload_qny(self,image): print("Qny upload.") #do something def download_qny(self,image): print("Qny download.") #do something
實際的開發(fā)中,代碼會有很多行,函數(shù)也不止三個,它被成百上千個地方被調(diào)用,分散在好幾百個文件中。 過了一段時間,公司自建了私有云,要求不能再使用七牛云了,要改成自己的云存儲,于是你不得不重新寫一個類:
class OwnImageStore(object): def upload_own(self,image): print("Qny upload.") #do something def download_own(self,image): print("Qny download.") #do something
然后你在使用七牛去的地方,都進行替換,還要替換函數(shù)的名稱,最后還要多次測試,生活哪一處沒有考慮到,運行報錯。好不容易改好了,突然有一天,需求又變了,由于自己的云服務經(jīng)常出問題,于是要換阿里云。經(jīng)過上次的一翻痛苦折騰,看到這次又要改,直接吐血。
其實問題就在于你寫的代碼不夠通用,命名不夠抽象。假如你的類的函數(shù)命名使用 upload,download 這樣,那么你修改的代碼量可能會減少到一半,只替換一下類名就可以了。實際上,我們可以使用接口來減少代碼的改動量:通過接口和實現(xiàn)相分離的模式,封裝不穩(wěn)定的實現(xiàn),暴露穩(wěn)定的接口。上下游系統(tǒng)在使用我們開發(fā)的功能時,只需要使用接口中聲明的函數(shù)列表,這樣當實現(xiàn)發(fā)生變化的時候,上游系統(tǒng)的代碼基本上不需要做改動,以此來降低耦合性,提高擴展性。下面就該問題,提供一種基于接口的代碼實現(xiàn)方式。
定義一個接口
from abc import ABCMeta, abstractmethod class ImageStore(metaclass = ABCMeta): @abstractmethod def upload(self,image): pass #raise NotImplementedError @abstractmethod def download(self,image): pass #raise NotImplementedError
定義了該接口之后,任何繼承該接口的類要想正確的使用,必須重寫 upload 和 download 方法,否則均會拋出異常,這里我們不需要自己在接口中拋出異常,標準庫 abc 已經(jīng)為我們做好了這些工作。
定義類,繼承接口
目的其實是是為了強制約束,也就是說必須實現(xiàn) upload 和 download 方法,在編譯時進行檢查,確保程序的健壯。
class OwnImageStore2(ImageStore): def upload(self,image): print("Own upload.") #do something def download(self,image): print("Own download.") #do something class QnyImageStore2(ImageStore): def getToken(): pass def upload(self,image): print("Qny upload.") def download(self,image): print("Qny download.") #do something
接下來,我們定義一個接口,可以自動的根據(jù)類型來選擇調(diào)用對應對象的方法。
class UsedStore(object): def __init__(self, store): if not isinstance(store, ImageStore): raise Exception('Bad interface') self._store = store def upload(self): self._store.upload('image') def download(self): self._store.download('image')
最后,我們可以在配置文件中指明我們使用的是哪個具體的接口:
#在其他文件中,應該這樣調(diào)用 img = QnyImageStore2() # img = OwnImageStore2() 把這些放在配置文件中,只需要更新配置文件就可以替換 store = UsedStore(img) store.upload() store.download()
這樣,后面再增加新的圖片存儲,我們只需要添加相應的類,繼承接口,并修改下配置文件即可,減輕大量的代碼修改工作。
Python 抽象基類的介紹 (PEP3119)
python 標準庫 abc,全稱是Abstract Base Classes,它提供以下功能:
- 一種重載isinstance()和issubclass()的方法
- 一個新的模塊abc作為“Abstract Base Classes支持框架”。它定義了一個用于abc的元類和一個可以用來定義抽象方法的裝飾器
- 容器和迭代器的特定抽象基類,將被添加到 collections 模塊
基本原理:
在面向?qū)ο蟪绦蛟O計領域,與對象交互的設計模式可以分為兩個基本類別,即“調(diào)用”和“檢查”。
調(diào)用是指通過調(diào)用對象的方法與對象進行交互。 通常,這會與多態(tài)性結合使用,因此調(diào)用給定方法可能會根據(jù)對象的類型運行不同的代碼。
檢查是指外部代碼(在對象的方法之外)檢查該對象的類型或?qū)傩裕⒏鶕?jù)該信息來決定如何處理該對象的能力。
兩種使用模式均服務于相同的通用目的,即能夠以統(tǒng)一的方式支持處理多種多樣且可能新穎的對象,但同時允許為每種不同類型的對象定制處理決策。
在經(jīng)典的 OOP 理論中,調(diào)用是首選的設計模式,并且不鼓勵檢查,因為檢查被認為是較早的過程編程風格的產(chǎn)物。 但是,實際上,這種觀點過于教條和僵化,導致了某種設計僵化,與諸如 Python 之類的語言的動態(tài)特性大相徑庭。
特別是,通常需要以對象類的創(chuàng)建者無法預期的方式處理對象。 內(nèi)置到滿足該對象的每個可能用戶需求的每個對象方法中,并非總是最佳的解決方案。 而且,有許多強大的調(diào)度哲學與嚴格地封裝在對象中的經(jīng)典OOP行為要求形成鮮明對比,例如規(guī)則或模式匹配驅(qū)動的邏輯
另一方面,經(jīng)典的 OOP 理論家對檢查的批評之一是缺乏形式主義和被檢查內(nèi)容的特殊性質(zhì)。 在諸如 Python 這樣的語言中,幾乎可以通過外部代碼反映并直接訪問對象的任何方面,有很多不同的方法來測試對象是否符合特定的協(xié)議。 例如,如果詢問“此對象是否是可變序列容器?”,則可以尋找“列表”的基類,或者可以尋找名為“ getitem”的方法。 但是請注意,盡管這些測試看似顯而易見,但它們都不正確,因為其中一個會產(chǎn)生假陰性,而另一個會產(chǎn)生假陽性。
普遍同意的補救措施是對測試進行標準化,并將其分組為正式形式。 通過繼承機制或其他某種方式,通過與每個類關聯(lián)一組標準的可測試屬性,最容易做到這一點。 每個測試都帶有一組承諾:它包含有關類的一般行為的承諾,以及有關其他可用的類方法的承諾。
PEP為組織這些測試提出了一種特殊的策略,稱為抽象基類(ABC)。 ABC只是添加到對象的繼承樹中的Python類,以將對象的某些功能發(fā)送給外部檢查器。 使用isinstance()完成測試,并且特定ABC的存在意味著測試已通過。
此外,ABC定義了建立該類型特征行為的最少方法集。 根據(jù)對象的ABC類型區(qū)分對象的代碼可以相信,這些方法將始終存在。 這些方法中的每一個都附帶有ABC文檔中描述的廣義抽象語義定義。 這些標準的語義定義不是強制性的,但強烈建議使用。
像Python中的所有其他內(nèi)容一樣,這些承諾屬于紳士協(xié)議的性質(zhì),在這種情況下,這意味著盡管該語言確實執(zhí)行了ABC中做出的某些承諾,但具體類的實現(xiàn)者必須確保 剩下的保留下來。
看完上面的描述,你可以簡單的理解為,ABC 是一個基類,繼承它,你可以寫一個類似于 java 的接口,接口中的方法將始終存在,可以放心使用,不需要再進行探測。
PEP3119 還給了樣例代碼讓你理解:
from abc import ABCMeta, abstractmethod class A(metaclass=ABCMeta): @abstractmethod def foo(self): pass A() # raises TypeError class B(A): pass B() # raises TypeError class C(A): def foo(self): print(42) C() # works
多的不說了,希望你可以正確地使用 ABC,同時強烈推薦,學習 Python,就看 Python 的官方文檔和 PEP 提案,這里有最權威的講解。
此外,設置模式也是非常重要的編程之術和編程之道,它是基本功,基本功如果不夠,把一臺戰(zhàn)斗機放你面前,你都不知道如何欣賞和品味。
掌握了設計模式,再看別人的代碼,你會擁有火眼金睛,哪些是戰(zhàn)斗機,哪些是拖拉機,對自己的學習和提升也非常有幫助,寫的代碼也會更加具有可維護性,可讀性,可擴展性,靈活性。
到此這篇關于如何使用Python基于接口編程的方法實現(xiàn)的文章就介紹到這了,更多相關Python基于接口編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
pytorch 實現(xiàn)在預訓練模型的 input上增減通道
今天小編就為大家分享一篇pytorch 實現(xiàn)在預訓練模型的 input上增減通道,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01jupyter notebook運行代碼沒反應且in[ ]沒有*
本文主要介紹了jupyter notebook運行代碼沒反應且in[ ]沒有*,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03詳解Django+Uwsgi+Nginx的生產(chǎn)環(huán)境部署
這篇文章主要介紹了Django + Uwsgi + Nginx 的生產(chǎn)環(huán)境部署,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06Pycharm無法使用已經(jīng)安裝Selenium的解決方法
今天小編就為大家分享一篇Pycharm無法使用已經(jīng)安裝Selenium的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10解決Python中pandas讀取*.csv文件出現(xiàn)編碼問題
很多朋友在使用Python中pandas讀取csv文件時,出現(xiàn)編碼格式問題,接下來通過本文給大家分享解決Python中pandas讀取*.csv文件出現(xiàn)編碼問題,需要的朋友可以參考下2019-07-07探索Python函數(shù)調(diào)用為何加速代碼執(zhí)行原理
Python 作為一種解釋型語言,其執(zhí)行速度相對于編譯型語言可能會較慢,然而,在Python中,通常觀察到代碼在函數(shù)中運行得更快的現(xiàn)象,這個現(xiàn)象主要是由于函數(shù)調(diào)用的內(nèi)部優(yōu)化和解釋器的工作方式導致的,本文將深入探討這個現(xiàn)象,并通過詳細的示例代碼進行解釋2024-01-01基于PyQt5實現(xiàn)狀態(tài)欄(statusBar)顯示和隱藏功能
這篇文章主要為大家詳細介紹了如何利用PyQt5實現(xiàn)狀態(tài)欄顯示和隱藏功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-08-08