獨家 | Python中的SOLID原則(附鏈接)
作者:Mattia Cinelli
翻譯:朱啟軒
校對:歐陽錦
SOLID原則是由Robert C. Martin提出的以首字母縮寫命名的編碼準(zhǔn)則,它代表了五種不同的編碼習(xí)慣。
如果您遵循這些原則,您就可以通過完善代碼的結(jié)構(gòu)和邏輯來提高代碼的可靠度。
Photo by ThisisEngineering RAEng on Unsplash
以下是SOLID的五大原則:
The Single-Responsibility Principle (SRP)
單一任務(wù)原則
The Open-Closed Principle (OCP)
開閉原則
The Liskov Substitution Principle (LSP)
Liskov替換原則
The Interface Segregation Principle (ISP)
界面分離原則
The Dependency inversion Principle (DIP)
從屬倒置原則
這五個原則并沒有一個特定的先后順序(做這個,然后做那個,等等),他們是歷經(jīng)幾十年發(fā)展而成的最佳組合。為了方便記憶,他們被用一個縮略詞(SOLID)所概括。類似的方法在計算機(jī)中也有出現(xiàn)過,例如: DRY: Don 't Repeat Yourself;KISS: Keep It Small and Simple;每一個首字母背后都是人們智慧的結(jié)晶。順便提一下,這個縮寫詞是在這五項原則建立多年后才產(chǎn)生的。
一般來說,SOLID原則是每個代碼開發(fā)人員的基本學(xué)習(xí)步驟,但通常它會被那些不把代碼質(zhì)量放在第一位的人所忽略。
然而,作為一名數(shù)據(jù)科學(xué)家,我認(rèn)為遵循這些原則是有益的。具體地說,它提高了代碼的可測試性,減少了技術(shù)上的障礙和為了實現(xiàn)客戶/股東的新需求而修改代碼所需的時間。
在下文里,我將探討這五個原則,并提供一些Python的示例。通常,SOLID原則應(yīng)用于面向?qū)ο蟮木幊糖榫爸校?Python的類),但我相信無論您的寫碼水平如何,他們都對您是有效的。我會在這里提供示例和解釋,面向的是“高級初學(xué)者”的級別。
1) 單一任務(wù)原則(SRP)
“一個類應(yīng)該有且只有一個可以被改變的理由”
換言之,代碼的每個部分((通常是一個類,但也可以是一個函數(shù)))應(yīng)該有且只有一個職責(zé)。因此,應(yīng)該只能有一個理由來改變它。
您經(jīng)常會看到一段代碼在處理整個進(jìn)程。即,在那些返回結(jié)果之前加載數(shù)據(jù)、修改數(shù)據(jù)并繪制數(shù)據(jù)圖的函數(shù)。
舉一個簡單的例子,我們有一串?dāng)?shù)組L = [n1, n2,…,nx],然后我們將對這個數(shù)組進(jìn)行一些數(shù)學(xué)運算。例如,計算平均值、中位數(shù)等。
比較糟糕的方法是讓一個函數(shù)做所有的工作:
為了使這個函數(shù)更符合SRP原則,我們應(yīng)該做的第一件事是將函數(shù)math_operations分解為只有單一功能的函數(shù)!這樣的話,這個函數(shù)的職責(zé)就不能再往下繼續(xù)去細(xì)分了。
第二步是創(chuàng)建一個函數(shù)(或類),一般命名為“main”。然后通過這個函數(shù)一步一步地調(diào)用所有其他函數(shù)。
現(xiàn)在,您就只有一個更改與“main”連接的函數(shù)的理由了。
這樣做的結(jié)果是:
1. 錯誤范圍縮小起來會更容易。進(jìn)程中的任何錯誤都更具有指向性,從而加速了調(diào)試進(jìn)程。
2. 代碼的任何部分都可以在其他地方再次使用。
3. 此外,經(jīng)常被忽視的一點是,這樣做使得函數(shù)測試起來更加容易。關(guān)于測試的附注:您應(yīng)該在實際編寫腳本之前就編寫好了測試。但是,為了創(chuàng)造一些好的結(jié)果并展示給利益相關(guān)者,這常常會被忽視。
對于第一個示例來說,這已經(jīng)是一個很大的改進(jìn)了。但是,創(chuàng)建一個“main”函數(shù)和調(diào)用只有單一責(zé)任的函數(shù)并沒有實現(xiàn)SR的全部原則。
事實上,我們的“main”有許多原因需要去改變。這個類實際上是很脆弱的,很難去維護(hù)。
為了解決這個問題,讓我們介紹下一個原則:
2) 開閉原則 (OCP
“軟件實體 … 應(yīng)該對拓展升級開放,對調(diào)試修改封閉”
也就是說:您不需要修改已經(jīng)編寫好的代碼以適應(yīng)新的需求,而只需添加您現(xiàn)在需要的東西。
這并不是說,當(dāng)代碼的前提需要修改時,您不用更改代碼,而是說,如果您需要添加與現(xiàn)有函數(shù)類似的新函數(shù),您不應(yīng)當(dāng)更改代碼的其他部分。
為了澄清這一點,讓我們參考前面看到的示例。如果我們想添加新的功能,例如,計算中位數(shù),我們應(yīng)該創(chuàng)建一個新的函數(shù),并將其調(diào)用添加到“main”中。這將增加一個擴(kuò)展函數(shù),同時也修改了“main”。
我們可以通過將我們編寫的所有函數(shù)轉(zhuǎn)換成一個類的子類的方法來解決這個問題。在本例中,我創(chuàng)建了一個名為“Operations”的抽象類和一個抽象方法“get_operation”。(抽象類這個知識點通常要求比較高。如果您不知道什么是抽象類,您可以先運行以下代碼進(jìn)行嘗試)。
現(xiàn)在,所有舊的函數(shù)和類都被__subclasses__()方法調(diào)用。它將找到所有從Operations繼承的類,并運行存在于所有子類中的函數(shù)“Operations”。
如果現(xiàn)在我們想要添加一個新的功能,例如:median,我們只需要添加一個從類“Operations”繼承的類“median”。新形成的子類將立即被__subclasses__()獲取,不需要對代碼的其他部分進(jìn)行修改。
我們將會得到一個非常靈活的類,它所需的維護(hù)時間也會是最少的。
3) Liskov 替換原則(LSP)
“那些使用指針或者引用基類的函數(shù)必須要在不知情的情況下使用派生類的對象”
也可以說,“派生類對于他們的基類來說必須要是可替換的才行”.
簡單地說,如果子類重新定義了同樣在父類中出現(xiàn)的函數(shù),用戶不應(yīng)該注意到任何差異,它只是基類的一個替代品。
例如,如果您正在使用一個函數(shù),而您的同事更改了基類,那您不應(yīng)該注意到任何差異。
在所有的SOLID原則中,這是最難理解和解釋的。對于這個原則,沒有標(biāo)準(zhǔn)的“教科書式的”解決方案,而且很難提供一個“標(biāo)準(zhǔn)示例”來展示。
我可以用最簡單的方式來概括這一原則:
如果在子類中重新定義了基類中也存在的函數(shù),那么這兩個函數(shù)應(yīng)該具有相同的行為。但是,這并不意味著它們必須強(qiáng)制性等同,而是:給定相同輸入能得出相同類型的結(jié)果。
在示例ocp.py中,“operation”方法出現(xiàn)在子類和基類中,終端用戶應(yīng)該期望從這兩個類中得到相同的行為。
這一原則的結(jié)果是,我們將以一致的方式編寫代碼,只有終端用戶會需要去了解我們的代碼是如何工作的。
附錄:
(您可以跳到下一個原則)。
LSP的一個結(jié)果是: 在子類中重新定義的新函數(shù)應(yīng)該是有效的,并且可能在父類中使用相同的函數(shù)時被調(diào)用。
這不是我們所常見的情況,事實上,通常我們?nèi)祟?,用集合論的方法來思考。我們的類會首先定義概念,然后用子類去擴(kuò)展之前定義的類的概念及其不同行為。
例如,超類“哺乳動物”的子類 “鴨嘴獸”就是一個例外,那就是這些哺乳動物會產(chǎn)卵。LSP原則告訴我們它將創(chuàng)建一個名為“give_birth”的函數(shù),這個函數(shù)將對子類Platypus和子類Dog有不同的行為。因此,我們應(yīng)該會有一個比哺乳類更抽象的基類來適應(yīng)這一點。
如果這聽起來非常令人困惑,請不要擔(dān)心,LSP原則在這方面的應(yīng)用很少有被實現(xiàn),目前還停留在理論階段。
4) 界面分離原則 (ISP
“具有很多面向特定客戶的界面會比只有一個的通用的界面要好”
在寫類的時候,我們一般都只考慮使用一個界面,所有的方法和屬性都“暴露”出來了,因此,所有用戶可以與之交互的東西都屬于這個類。
在這種意義上,IS原則告訴我們,類其實有所需的界面(SRP)就行了,我們應(yīng)該避免那些無法工作或沒有理由成為該類一部分的方法。
當(dāng)子類從它不需要的基類繼承方法時,就會出現(xiàn)這個問題。
讓我們來看一個例子:
對于這個例子,我們有一個抽象類 “Mammal”,它有兩個抽象方法:“walk”和“swim”。這兩個元素將屬于“Human”子類,而只有“swim”將屬于“Whale”子類。
實際上,如果我們運行這段代碼,我們會得到:
子類whale仍然可以調(diào)用方法“walk”,但它不應(yīng)該這樣做,我們必須避免它。
ISP建議的方法是創(chuàng)建更多面向特定用戶的界面,而不是一個通用的界面。因此,我們的代碼示例變成如下:
現(xiàn)在,每個子類只繼承它需要的東西,避免了調(diào)用斷章取義(錯誤)的子方法。如果繼承了不需要的東西,可能會產(chǎn)生難以捕捉的錯誤。
這一原則與其他原則緊密相連,具體來說,它告訴我們要保證子類的內(nèi)容整潔度,排除對子類沒有用處的元素。這樣做的最終目的是讓我們的類能夠保持整潔,并將錯誤最小化。
5) 從屬倒置原則(DIP)
“抽象類不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象類。高級模塊不應(yīng)該依賴于低級模塊。兩者都應(yīng)該依賴于抽象類”
因此,抽象類(例如,上面看到的界面)不應(yīng)該依賴于低級方法,而應(yīng)該都依賴于第三個界面。
為了更好地解釋這個概念,我傾向于認(rèn)為這是一種信息流。
假設(shè)您有一個程序,它接收一組特定的信息(文件、格式等),然后您編寫了一個腳本來處理它。
如果這些信息有變化會發(fā)生什么?
你將不得不重寫你的腳本并調(diào)整新的格式。失去與舊文件的兼容性。
然而,您可以通過創(chuàng)建第三個抽象類來解決這個問題,該抽象類將信息作為輸入并將其傳遞給其他抽象類。
這基本上也是API的用途。
這一原則的設(shè)計理念有趣在于,它與我們通常的做法相反。
考慮到DIP原則,我們將從項目的尾部開始,我們的代碼獨立于所輸入的內(nèi)容,它不受更改的影響,并且不受我們的直接控制。
我希望您能在您的代碼中運用這些概念,我知道它們是為我準(zhǔn)備的。除此以外,我還提供了一些我用來理解這些原則的材料。
R. C. Martin, “The Principles of OOD,” 2013.
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
https://codingwithjohan.com/blog/solid-python-introduction
“Clean Code in Python” by Mariano Anaya
原文標(biāo)題:
SOLID Coding in Python
原文鏈接:
https://towardsdatascience.com/solid-coding-in-python-1281392a6a94
*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。