博客專欄

EEPW首頁(yè) > 博客 > 獨(dú)家 | Python中的SOLID原則(附鏈接)

獨(dú)家 | Python中的SOLID原則(附鏈接)

發(fā)布人:數(shù)據(jù)派THU 時(shí)間:2021-09-19 來(lái)源:工程師 發(fā)布文章

作者:Mattia Cinelli

翻譯:朱啟軒

校對(duì):歐陽(yáng)錦

SOLID原則是由Robert C. Martin提出的以首字母縮寫命名的編碼準(zhǔn)則,它代表了五種不同的編碼習(xí)慣。

如果您遵循這些原則,您就可以通過(guò)完善代碼的結(jié)構(gòu)和邏輯來(lái)提高代碼的可靠度。

1.png

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)

從屬倒置原則

這五個(gè)原則并沒有一個(gè)特定的先后順序(做這個(gè),然后做那個(gè),等等),他們是歷經(jīng)幾十年發(fā)展而成的最佳組合。為了方便記憶,他們被用一個(gè)縮略詞(SOLID)所概括。類似的方法在計(jì)算機(jī)中也有出現(xiàn)過(guò),例如: DRY: Don 't Repeat Yourself;KISS: Keep It Small and Simple;每一個(gè)首字母背后都是人們智慧的結(jié)晶。順便提一下,這個(gè)縮寫詞是在這五項(xiàng)原則建立多年后才產(chǎn)生的。

一般來(lái)說(shuō),SOLID原則是每個(gè)代碼開發(fā)人員的基本學(xué)習(xí)步驟,但通常它會(huì)被那些不把代碼質(zhì)量放在第一位的人所忽略。

然而,作為一名數(shù)據(jù)科學(xué)家,我認(rèn)為遵循這些原則是有益的。具體地說(shuō),它提高了代碼的可測(cè)試性,減少了技術(shù)上的障礙和為了實(shí)現(xiàn)客戶/股東的新需求而修改代碼所需的時(shí)間。

在下文里,我將探討這五個(gè)原則,并提供一些Python的示例。通常,SOLID原則應(yīng)用于面向?qū)ο蟮木幊糖榫爸校?Python的類),但我相信無(wú)論您的寫碼水平如何,他們都對(duì)您是有效的。我會(huì)在這里提供示例和解釋,面向的是“高級(jí)初學(xué)者”的級(jí)別。

1) 單一任務(wù)原則(SRP)

“一個(gè)類應(yīng)該有且只有一個(gè)可以被改變的理由”

換言之,代碼的每個(gè)部分((通常是一個(gè)類,但也可以是一個(gè)函數(shù)))應(yīng)該有且只有一個(gè)職責(zé)。因此,應(yīng)該只能有一個(gè)理由來(lái)改變它。

您經(jīng)常會(huì)看到一段代碼在處理整個(gè)進(jìn)程。即,在那些返回結(jié)果之前加載數(shù)據(jù)、修改數(shù)據(jù)并繪制數(shù)據(jù)圖的函數(shù)。

舉一個(gè)簡(jiǎn)單的例子,我們有一串?dāng)?shù)組L = [n1, n2,…,nx],然后我們將對(duì)這個(gè)數(shù)組進(jìn)行一些數(shù)學(xué)運(yùn)算。例如,計(jì)算平均值、中位數(shù)等。

比較糟糕的方法是讓一個(gè)函數(shù)做所有的工作:

2.png

為了使這個(gè)函數(shù)更符合SRP原則,我們應(yīng)該做的第一件事是將函數(shù)math_operations分解為只有單一功能的函數(shù)!這樣的話,這個(gè)函數(shù)的職責(zé)就不能再往下繼續(xù)去細(xì)分了。

第二步是創(chuàng)建一個(gè)函數(shù)(或類),一般命名為“main”。然后通過(guò)這個(gè)函數(shù)一步一步地調(diào)用所有其他函數(shù)。

3.png

現(xiàn)在,您就只有一個(gè)更改與“main”連接的函數(shù)的理由了。

這樣做的結(jié)果是:

1. 錯(cuò)誤范圍縮小起來(lái)會(huì)更容易。進(jìn)程中的任何錯(cuò)誤都更具有指向性,從而加速了調(diào)試進(jìn)程。

2. 代碼的任何部分都可以在其他地方再次使用。

3. 此外,經(jīng)常被忽視的一點(diǎn)是,這樣做使得函數(shù)測(cè)試起來(lái)更加容易。關(guān)于測(cè)試的附注:您應(yīng)該在實(shí)際編寫腳本之前就編寫好了測(cè)試。但是,為了創(chuàng)造一些好的結(jié)果并展示給利益相關(guān)者,這常常會(huì)被忽視。

對(duì)于第一個(gè)示例來(lái)說(shuō),這已經(jīng)是一個(gè)很大的改進(jìn)了。但是,創(chuàng)建一個(gè)“main”函數(shù)和調(diào)用只有單一責(zé)任的函數(shù)并沒有實(shí)現(xiàn)SR的全部原則。

事實(shí)上,我們的“main”有許多原因需要去改變。這個(gè)類實(shí)際上是很脆弱的,很難去維護(hù)。

為了解決這個(gè)問(wèn)題,讓我們介紹下一個(gè)原則:

2) 開閉原則 (OCP

“軟件實(shí)體 … 應(yīng)該對(duì)拓展升級(jí)開放,對(duì)調(diào)試修改封閉”

也就是說(shuō):您不需要修改已經(jīng)編寫好的代碼以適應(yīng)新的需求,而只需添加您現(xiàn)在需要的東西。

這并不是說(shuō),當(dāng)代碼的前提需要修改時(shí),您不用更改代碼,而是說(shuō),如果您需要添加與現(xiàn)有函數(shù)類似的新函數(shù),您不應(yīng)當(dāng)更改代碼的其他部分。

為了澄清這一點(diǎn),讓我們參考前面看到的示例。如果我們想添加新的功能,例如,計(jì)算中位數(shù),我們應(yīng)該創(chuàng)建一個(gè)新的函數(shù),并將其調(diào)用添加到“main”中。這將增加一個(gè)擴(kuò)展函數(shù),同時(shí)也修改了“main”。

我們可以通過(guò)將我們編寫的所有函數(shù)轉(zhuǎn)換成一個(gè)類的子類的方法來(lái)解決這個(gè)問(wèn)題。在本例中,我創(chuàng)建了一個(gè)名為“Operations”的抽象類和一個(gè)抽象方法“get_operation”。(抽象類這個(gè)知識(shí)點(diǎn)通常要求比較高。如果您不知道什么是抽象類,您可以先運(yùn)行以下代碼進(jìn)行嘗試)。

現(xiàn)在,所有舊的函數(shù)和類都被__subclasses__()方法調(diào)用。它將找到所有從Operations繼承的類,并運(yùn)行存在于所有子類中的函數(shù)“Operations”。

4.png

如果現(xiàn)在我們想要添加一個(gè)新的功能,例如:median,我們只需要添加一個(gè)從類“Operations”繼承的類“median”。新形成的子類將立即被__subclasses__()獲取,不需要對(duì)代碼的其他部分進(jìn)行修改。

我們將會(huì)得到一個(gè)非常靈活的類,它所需的維護(hù)時(shí)間也會(huì)是最少的。

3) Liskov 替換原則(LSP)

“那些使用指針或者引用基類的函數(shù)必須要在不知情的情況下使用派生類的對(duì)象”

也可以說(shuō),“派生類對(duì)于他們的基類來(lái)說(shuō)必須要是可替換的才行”.

簡(jiǎn)單地說(shuō),如果子類重新定義了同樣在父類中出現(xiàn)的函數(shù),用戶不應(yīng)該注意到任何差異,它只是基類的一個(gè)替代品。

例如,如果您正在使用一個(gè)函數(shù),而您的同事更改了基類,那您不應(yīng)該注意到任何差異。

在所有的SOLID原則中,這是最難理解和解釋的。對(duì)于這個(gè)原則,沒有標(biāo)準(zhǔn)的“教科書式的”解決方案,而且很難提供一個(gè)“標(biāo)準(zhǔn)示例”來(lái)展示。

我可以用最簡(jiǎn)單的方式來(lái)概括這一原則:

如果在子類中重新定義了基類中也存在的函數(shù),那么這兩個(gè)函數(shù)應(yīng)該具有相同的行為。但是,這并不意味著它們必須強(qiáng)制性等同,而是:給定相同輸入能得出相同類型的結(jié)果。

在示例ocp.py中,“operation”方法出現(xiàn)在子類和基類中,終端用戶應(yīng)該期望從這兩個(gè)類中得到相同的行為。

這一原則的結(jié)果是,我們將以一致的方式編寫代碼,只有終端用戶會(huì)需要去了解我們的代碼是如何工作的。

附錄:

(您可以跳到下一個(gè)原則)。

LSP的一個(gè)結(jié)果是: 在子類中重新定義的新函數(shù)應(yīng)該是有效的,并且可能在父類中使用相同的函數(shù)時(shí)被調(diào)用。

這不是我們所常見的情況,事實(shí)上,通常我們?nèi)祟?,用集合論的方法?lái)思考。我們的類會(huì)首先定義概念,然后用子類去擴(kuò)展之前定義的類的概念及其不同行為。

例如,超類“哺乳動(dòng)物”的子類 “鴨嘴獸”就是一個(gè)例外,那就是這些哺乳動(dòng)物會(huì)產(chǎn)卵。LSP原則告訴我們它將創(chuàng)建一個(gè)名為“give_birth”的函數(shù),這個(gè)函數(shù)將對(duì)子類Platypus和子類Dog有不同的行為。因此,我們應(yīng)該會(huì)有一個(gè)比哺乳類更抽象的基類來(lái)適應(yīng)這一點(diǎn)。

如果這聽起來(lái)非常令人困惑,請(qǐng)不要擔(dān)心,LSP原則在這方面的應(yīng)用很少有被實(shí)現(xiàn),目前還停留在理論階段。

4) 界面分離原則 (ISP

“具有很多面向特定客戶的界面會(huì)比只有一個(gè)的通用的界面要好”

在寫類的時(shí)候,我們一般都只考慮使用一個(gè)界面,所有的方法和屬性都“暴露”出來(lái)了,因此,所有用戶可以與之交互的東西都屬于這個(gè)類。

在這種意義上,IS原則告訴我們,類其實(shí)有所需的界面(SRP)就行了,我們應(yīng)該避免那些無(wú)法工作或沒有理由成為該類一部分的方法。

當(dāng)子類從它不需要的基類繼承方法時(shí),就會(huì)出現(xiàn)這個(gè)問(wèn)題。

讓我們來(lái)看一個(gè)例子:

5.png

對(duì)于這個(gè)例子,我們有一個(gè)抽象類 “Mammal”,它有兩個(gè)抽象方法:“walk”和“swim”。這兩個(gè)元素將屬于“Human”子類,而只有“swim”將屬于“Whale”子類。

實(shí)際上,如果我們運(yùn)行這段代碼,我們會(huì)得到:

6.png

子類whale仍然可以調(diào)用方法“walk”,但它不應(yīng)該這樣做,我們必須避免它。

ISP建議的方法是創(chuàng)建更多面向特定用戶的界面,而不是一個(gè)通用的界面。因此,我們的代碼示例變成如下:

7.png

現(xiàn)在,每個(gè)子類只繼承它需要的東西,避免了調(diào)用斷章取義(錯(cuò)誤)的子方法。如果繼承了不需要的東西,可能會(huì)產(chǎn)生難以捕捉的錯(cuò)誤。

這一原則與其他原則緊密相連,具體來(lái)說(shuō),它告訴我們要保證子類的內(nèi)容整潔度,排除對(duì)子類沒有用處的元素。這樣做的最終目的是讓我們的類能夠保持整潔,并將錯(cuò)誤最小化。

5) 從屬倒置原則(DIP)

“抽象類不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象類。高級(jí)模塊不應(yīng)該依賴于低級(jí)模塊。兩者都應(yīng)該依賴于抽象類”

因此,抽象類(例如,上面看到的界面)不應(yīng)該依賴于低級(jí)方法,而應(yīng)該都依賴于第三個(gè)界面。

為了更好地解釋這個(gè)概念,我傾向于認(rèn)為這是一種信息流。

假設(shè)您有一個(gè)程序,它接收一組特定的信息(文件、格式等),然后您編寫了一個(gè)腳本來(lái)處理它。

如果這些信息有變化會(huì)發(fā)生什么?

你將不得不重寫你的腳本并調(diào)整新的格式。失去與舊文件的兼容性。

然而,您可以通過(guò)創(chuàng)建第三個(gè)抽象類來(lái)解決這個(gè)問(wèn)題,該抽象類將信息作為輸入并將其傳遞給其他抽象類。

這基本上也是API的用途。

8.png

這一原則的設(shè)計(jì)理念有趣在于,它與我們通常的做法相反。

考慮到DIP原則,我們將從項(xiàng)目的尾部開始,我們的代碼獨(dú)立于所輸入的內(nèi)容,它不受更改的影響,并且不受我們的直接控制。

我希望您能在您的代碼中運(yùn)用這些概念,我知道它們是為我準(zhǔn)備的。除此以外,我還提供了一些我用來(lái)理解這些原則的材料。

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)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。



關(guān)鍵詞: AI

相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉