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