最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【翻譯】SOLID原則:提升Python中的面向?qū)ο笤O(shè)計(jì)

2023-08-16 10:57 作者:月愿LW  | 我要投稿

原文鏈接:https://realpython.com/solid-principles-python/

by Leodanis Pozo Ramos May 01, 2023


目錄

  • Python中的面向?qū)ο笤O(shè)計(jì):SOLID原則

  • 單一職責(zé)原則(SRP)

  • 開放封閉原則(OCP)

  • 里氏替換原則(LSP)

  • 接口隔離原則(ISP)

  • 依賴倒置原則(DIP)

  • 結(jié)語


當(dāng)你使用面向?qū)ο缶幊蹋∣OP)建立起來一個(gè)Python項(xiàng)目,規(guī)劃好不同的類、對(duì)象之間的交互以解決特定問題是工作的重要部分。這種規(guī)劃就被稱作 object-oriented design (OOD) (面向?qū)ο笤O(shè)計(jì)),把它做好可是個(gè)不小的挑戰(zhàn)。如果你在設(shè)計(jì)Python類時(shí)卡住了,那么SOLID原則會(huì)幫你走出困境。

SOLID是一套共計(jì)5個(gè)的面向?qū)ο蟮脑O(shè)計(jì)原則,能幫助你設(shè)計(jì)出優(yōu)秀的、結(jié)構(gòu)清晰的類,進(jìn)而寫出更容易維護(hù)、更靈活、伸縮性強(qiáng)的代碼。這些教程是面向?qū)ο笤O(shè)計(jì)最佳實(shí)踐的根本部分。

在本篇教程中,你將

  • 理解每一條SOLID原則意義目的

  • 識(shí)別違反了部分SOLID原則的Python代碼

  • 運(yùn)用SOLID原則來重塑你的Python代碼并提升它的設(shè)計(jì)

在這次學(xué)習(xí)之旅中,你會(huì)編寫實(shí)際的案例,來探索如何在SOLID原則的指導(dǎo)下寫出條理性強(qiáng)、靈活、維護(hù)性強(qiáng)以及伸縮性強(qiáng)的代碼。

為了在最大程度上受益,你必須對(duì)Python的 object-oriented programming (面向?qū)ο缶幊蹋└拍钣休^好的了解,例如 classesinterfaces ,和 ?inheritance


Python中的面向?qū)ο笤O(shè)計(jì):SOLID原則

當(dāng)在Python里編寫類和設(shè)計(jì)它們的交互時(shí),你可以遵循一系列幫你編寫更好的面向?qū)ο蟠a的原則。這其中最受歡迎、最廣泛接受的 object-oriented design (OOD) (面向?qū)ο笤O(shè)計(jì))標(biāo)準(zhǔn)之一就是 SOLID 原則。

如果你是從 C++ 或者 Java 轉(zhuǎn)過來的,你可能已經(jīng)對(duì)這些原則很熟悉了。也許你在想SOLID原則是否對(duì)Python代碼也適用。這個(gè)問題的答案,當(dāng)然是一個(gè)響亮的 yes 。如果你在編寫面向?qū)ο蟠a,那就應(yīng)該把這些原則考慮進(jìn)去。

但SOLID原則有哪些呢?SOLID是五個(gè)應(yīng)用在面向?qū)ο笤O(shè)計(jì)的核心原則的縮寫。這些原則如下:

  1. Single-responsibility principle (SRP)(單一職責(zé)原則)

  2. Open–closed principle (OCP)(開放封閉原則)

  3. Liskov substitution principle (LSP)(里氏替換原則)

  4. Interface segregation principle (ISP)(接口隔離原則)

  5. Dependency inversion principle (DIP)(依賴倒置原則)

你將詳細(xì)探索這些原則,并編寫一些在Python中應(yīng)用它們的真實(shí)代碼。在這個(gè)過程中,通過應(yīng)用SOLID原則,你將對(duì)如何編寫更直觀、有條理、伸縮性強(qiáng)、復(fù)用性強(qiáng)的面向?qū)ο蟠a有深入的理解。閑話少說,你要開始學(xué)清單上的第一條原則啦。


單一職責(zé)原則 (SRP)

單一職責(zé)原則 (SRP)Robert C. Martin 提出,他的昵稱 Uncle Bob 更廣為人知。他在軟件工程界廣受尊敬,還是 Agile Manifesto (敏捷宣言)的簽署者之一。事實(shí)上,他創(chuàng)造了SOLID這個(gè)術(shù)語。

單一職責(zé)原則規(guī)定了這點(diǎn):

一個(gè)類應(yīng)該只有一個(gè)發(fā)生變化的原因

這就是說一個(gè)類應(yīng)該只有一個(gè)職責(zé),它的方法也應(yīng)該表現(xiàn)成這樣。如果一個(gè)類關(guān)注了多個(gè)任務(wù),那么你應(yīng)該把這些任務(wù)拆成單個(gè)單個(gè)的類。

注意: 你會(huì)發(fā)現(xiàn)SOLID原則的表述形式會(huì)有差別。本篇教程談的是 Uncle Bob 在他的書 Agile Software Development: Principles, Patterns, and Practices 里用的表述。所以,所有的直接引用都來自他的書。

如果你想看看這些,以及相關(guān)原則的快速概述的其他表述,可以看看 Uncle Bob’s The Principles of OOD。

這個(gè)原則和 separation of concerns (責(zé)任分離)很相近,責(zé)任分離建議把程序拆成不同部分。每個(gè)部分得強(qiáng)調(diào)單獨(dú)的責(zé)任。

為了說明單一職責(zé)原則以及它如何提升你的面向?qū)ο笤O(shè)計(jì),就比如說有一個(gè)這樣的 FileManager 類:

在這個(gè)例子中,你的 FileManager 類有兩個(gè)不同的職責(zé)。它用 .read().write() 方法來管理文件。也通過提供 .compress().decompress() 方法處理 ZIP archives (ZIP文檔)。

這個(gè)類違反了單一職責(zé)原則因?yàn)樗袃蓚€(gè)改變內(nèi)部實(shí)現(xiàn)的原因。為了修正這個(gè)問題,讓你的設(shè)計(jì)魯棒性更好,你可以把這個(gè)類拆成兩個(gè)更小、更專一的類,每個(gè)只有它自己的特定職責(zé):

現(xiàn)在你有了兩個(gè)更小的類,每個(gè)都僅有一個(gè)職責(zé)。 FileManager 關(guān)心的是管理文件,而 ZipFileManager 處理ZIF格式文件的 compression (壓縮)和 decompression (解壓)。 這兩個(gè)類更小,所以更容易管理。它們也更容易分析、測(cè)試以及debug。

此處的職責(zé)可能是相當(dāng)主觀的。具有單一職責(zé)并不一定意味著具有單個(gè)方法。職責(zé)不是直接跟方法數(shù)綁定的,而是跟你的類負(fù)責(zé)的核心任務(wù)綁定,取決于這個(gè)類在你的代碼里代表什么。然而,主觀性并不應(yīng)該阻止你力求SRP。


開放封閉原則(OCP)

面向?qū)ο笤O(shè)計(jì)里的開放封閉原則(OCP)最初由 Bertrand Meyer 在1988年提出,意思是:

軟件實(shí)體(類、模塊、函數(shù)等等)應(yīng)該對(duì)拓展開放,對(duì)修改封閉

要理解什么是開放封閉原則,考慮下面的 Shape 類:

Shape 的構(gòu)造器接收一個(gè)在 "rectangle""circle" 二選一的 shape_type 參數(shù)。還用 **kwargs 語法接收特定的一套關(guān)鍵字參數(shù)。如果你把形狀設(shè)成 "rectangle" ,那么你應(yīng)該傳入 widthheight 關(guān)鍵字參數(shù)以便構(gòu)造一個(gè)合適的矩形。

相反,如果你把形狀設(shè)成 "circle" ,那么你必須也傳入 radius 參數(shù)來構(gòu)造一個(gè)圓形。

注意: 這個(gè)例子可能看起來有點(diǎn)極端。這是為了清晰地展示開放封閉原則的核心觀點(diǎn)。

Shape 還有一個(gè) .calculate_area() 方法,能根據(jù)當(dāng)前形狀的 .shape_type 計(jì)算面積:

這個(gè)類能運(yùn)行。你可以創(chuàng)建圓和矩形,計(jì)算面積,等等。然而,這個(gè)類看起來很糟糕,有些事似乎第一眼就不對(duì)。

想象你需要加入一個(gè)新的形狀,也許是正方形。你會(huì)怎么做?好吧,其中一個(gè)選擇是再加個(gè)elif 子句到 .__init__().calculate_area() 里,這樣就能解決正方形的需求了。

必須做這些改變來創(chuàng)建新的形狀就說明你的類對(duì)修改是開放的。這違反了開放封閉原則。你該如何修改類使得它對(duì)拓展開放但對(duì)修改封閉呢?這有一個(gè)可能的解決:

在這段代碼里,你完全 refactored (重塑)了 Shape 類,把它變成了一個(gè) abstract base class (ABC)(抽象基類)。這個(gè)類提供的 interface (API) (接口)讓你能定義任何想要的形狀。這個(gè) interface (接口)由 .shape_type 屬性和必須在子類重寫的 .calculate_area() 方法組成。

注意:上面的這個(gè)例子以及后續(xù)章節(jié)的一些例子使用了Python的ABC來提供接口繼承。在這種繼承里,子類繼承了接口而不是功能。反之,如果類繼承了功能,就是實(shí)現(xiàn)繼承

這種更新讓類對(duì)修改封閉?,F(xiàn)在你無需修改 Shape 就能往類里加入新的形狀啦。在每種情況下,你都必須實(shí)現(xiàn)所需接口,這也使得你的類具有 polymorphic (多態(tài)性)。


里氏替換原則(LSP)

里氏替換原則(LSP)Barbara Liskov 在1987年的 OOPSLA conference (OOPSLA會(huì)議)提出。從此,這個(gè)原則成了面向?qū)ο缶幊痰幕静糠?。這條原則規(guī)定了:

子類必須能替代它們的基類

舉個(gè)例子,如果你有一段用 Shape 類運(yùn)行的代碼,那么也應(yīng)該能把這個(gè)類替換成它的任意一個(gè)子類,例如 CircleRectangle,而不會(huì)中途報(bào)錯(cuò)。

注意:你可以閱讀 conference proceedings (會(huì)議議程),了解 Barbara Liskov 首次分享這一原則的主題演講內(nèi)容;或者還可以觀看她的一段簡短 interview (訪談)片段,以獲取更多背景信息。

實(shí)際上,這個(gè)原則是說當(dāng)任何人調(diào)用子類和基類的同一個(gè)方法時(shí),它們的行為應(yīng)該如預(yù)期般一致。繼續(xù)形狀的例子,比如說你有一個(gè)下面這樣的 Rectangle 類:

Rectangle 里,你提供了 .calculate_area() 方法,該方法使用了 .width.height instance attributes(實(shí)例屬性)。

由于一個(gè)正方形是等邊的特殊矩形,為了復(fù)用相同代碼,你考慮從 Rectangle 派生出 Square 。然后,你為 .width.height 屬性重寫了 setter ?,這樣當(dāng)一條邊改變,另一條邊也會(huì)改變:

在這一小段代碼中,你定義 Rectangle 的子類 Square 。作為用戶可能會(huì)希望,類構(gòu)造器 constructor 只接收正方形變成作為參數(shù)。 在內(nèi)部, .__init__() 方法使用 side 參數(shù)初始化了父類的 .width.height 屬性。

你可能也會(huì)定義 special method (特殊方法) .__setattr__() 來接入Python的屬性設(shè)置機(jī)制并在給 .width.height 屬性 assignment (設(shè)定)新值時(shí)攔截。 特別地,當(dāng)設(shè)定其中一個(gè)屬性,另外一個(gè)屬性也會(huì)被設(shè)成同樣的值:

現(xiàn)在你確保了 Square 對(duì)象永遠(yuǎn)是一個(gè)有效的矩形,雖然浪費(fèi)了一點(diǎn)內(nèi)存,但感覺舒服多了。不幸的是,這違反了里氏替換原則因?yàn)槟悴荒軐?Rectangle 的實(shí)例替換成對(duì)應(yīng)的 Square 。

當(dāng)別人想在代碼里用矩形對(duì)象,他們可能假定這個(gè)對(duì)象表現(xiàn)得像一個(gè)矩形,即會(huì)暴露出兩個(gè)獨(dú)立的 .width.height 屬性。 與此同時(shí),你的 Square 類更改了由對(duì)象接口所保證的行為,從而打斷了這種假設(shè)。這可能導(dǎo)致意料之外的結(jié)果,很難去 debug 。

雖然正方形在數(shù)學(xué)上是一類特殊的矩形,但如果你想遵守里氏替換原則,它們的類不應(yīng)該是父子代關(guān)系。其中一個(gè)解決方案是給 RectangleSquare 創(chuàng)建一個(gè)可拓展的基類:

現(xiàn)在你可以通過多態(tài)性用 RectangleSquare 替換 Shape ,現(xiàn)在它們是兄弟姐妹而不是父子代關(guān)系。注意兩個(gè)具體的形狀都有不同的一套屬性、不同的初始化方法,并可能實(shí)現(xiàn)更多不相干的行為。他們唯一的共同點(diǎn)就是都能計(jì)算面積。

有了這個(gè)實(shí)現(xiàn),當(dāng)你只關(guān)心它們的共同行為時(shí),就可以用 SquareRectangle 子類交換 Shape

這里,你給計(jì)算總面積的函數(shù)傳入了一個(gè)矩形和正方形。由于這個(gè)函數(shù)只關(guān)心 .calculate_area() 方法,形狀不同也沒關(guān)系。這就是里氏替換原則的精華。


接口隔離原則(ISP)

接口隔離原則(ISP)和單一職責(zé)原則出發(fā)點(diǎn)相同。 是的,這又是 Uncle Bob’s 的另一項(xiàng)成就。這個(gè)原則的中心思想是:

clients 不應(yīng)該被強(qiáng)迫依賴它們用不上的方法。Interfaces 屬于clients,而不是 hierarchy (層次結(jié)構(gòu))。

在這個(gè)情景里,clients 指的是類和子類,而 interfaces 包括了方法和屬性。換句話說,如果一個(gè)類用不上某些方法或?qū)傩?,那么這些方法和屬性就該拆分到更多專門的類里。

考慮下面這個(gè)類的 hierarchy (層次結(jié)構(gòu)),用于模仿打印機(jī)設(shè)備:

在這個(gè)例子中,基類 Printer ,提供了子類必須實(shí)現(xiàn)的接口。繼承自 PrinterOldPrinter 必須實(shí)現(xiàn)相同的接口。然而, OldPrinter 不使用 .fax().scan() 方法因?yàn)檫@種打印機(jī)不支持這些功能。

這個(gè)實(shí)現(xiàn)違反了 ISP 因?yàn)樗鼜?qiáng)迫 OldPrinter 暴露沒實(shí)現(xiàn)也用不上的接口。要修正這點(diǎn),你應(yīng)該把接口分成更小、更專門的類。然后你就可以按照需要繼承多個(gè)接口類來創(chuàng)建具體類:

現(xiàn)在 Printer, Fax,和 Scanner 是提供特定接口的基類。要?jiǎng)?chuàng)建 OldPrinter ,你只需要繼承 Printer 接口。這樣,這個(gè)類就不會(huì)有用不上的方法了。要?jiǎng)?chuàng)建 ModernPrinter 類,你需要繼承所有的接口。簡單地說,你隔離了 Printer 接口。

這種類設(shè)計(jì)允許你創(chuàng)建具有不同功能的各種機(jī)器,使得你的設(shè)計(jì)更靈活、拓展性好。


依賴倒置原則(DIP)

依賴倒置原則(DIP)是SOLID里的最后一個(gè)。這個(gè)原則規(guī)定:

抽象不該依賴細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴抽象。

聽起來很復(fù)雜。這兒有個(gè)例子幫你明白這點(diǎn)。比如說你正在創(chuàng)建一個(gè)應(yīng)用程序,有一個(gè) FrontEnd 類,用來把數(shù)據(jù)友好地展示給用戶。這個(gè)應(yīng)用程序目前從數(shù)據(jù)庫獲取數(shù)據(jù),所以你最終寫出了下面代碼:

在這個(gè)例子中, FrontEnd 類依賴于 BackEnd 類和它的具體實(shí)現(xiàn)。你可以說這兩個(gè)類緊密耦合在了一起。這種耦合會(huì)導(dǎo)致伸縮性上的問題。比如說你的應(yīng)用程序發(fā)展很快,你希望它能從 REST API 獲取數(shù)據(jù)。你會(huì)怎么做呢?

你可能想到給 BackEnd 加個(gè)新方法來從 REST API 獲取數(shù)據(jù)。然而,這又使得你去修改 FrontEnd ,而根據(jù) open-closed principle (開放封閉原則),它本應(yīng)對(duì)修改封閉。

要修正這個(gè)問題,你可以應(yīng)用依賴倒置原則,使你的類依賴抽象而不是像 BackEnd 這樣的具體實(shí)現(xiàn)。在這個(gè)特定例子中,你可以引入一個(gè) DataSource 類來提供在具體類中使用的接口:

在這個(gè)重新設(shè)計(jì)的版本里,你加入了 DataSource 類作為提供所需接口或者說 .get_data() 方法的抽象類。注意 FrontEnd 現(xiàn)在依賴于 DataSource 提供的抽象接口。

然后你定義了 Database 類,作為從數(shù)據(jù)庫獲取數(shù)據(jù)的情景的具體實(shí)現(xiàn)。這個(gè)類通過繼承依賴于 DataSource 抽象類。最后,你定義了 API 類以支持從 REST API 獲取數(shù)據(jù)。這個(gè)類也依賴于 DataSource 抽象類。

現(xiàn)在你可以在代碼里這么用 FrontEnd 類:

這里,你先使用一個(gè)Database 對(duì)象初始化 FrontEnd ,然后又換了一個(gè) API 對(duì)象。每次你調(diào)用 .display_data() ,結(jié)果都依賴于你使用的具體數(shù)據(jù)源。注意你可也以通過給 FrontEnd 實(shí)例重新賦值 .data_source 屬性來動(dòng)態(tài)改變數(shù)據(jù)源。


結(jié)語

關(guān)于五條SOLID原則,你已經(jīng)學(xué)到了很多,包括如何分辨違反它們的代碼以及如何重塑代碼來遵守最佳設(shè)計(jì)實(shí)踐。你見識(shí)到了有關(guān)每條原則的正面和反面案例,也學(xué)到了在Python里應(yīng)用SOLID原則能提升你的面向?qū)ο笤O(shè)計(jì)。

在本篇教程中,你學(xué)到了:

  • 理解每一條SOLID原則意義目的

  • 識(shí)別違反了部分SOLID原則的Python代碼

  • 運(yùn)用SOLID原則來重塑你的Python代碼并提升它的設(shè)計(jì)

有了這些知識(shí),你就對(duì)一些確立已久的最佳實(shí)踐打下了堅(jiān)實(shí)的基礎(chǔ),當(dāng)你設(shè)計(jì)Python中的類及其關(guān)系時(shí),應(yīng)該把它們應(yīng)用其中。通過應(yīng)用這些原則,你可以創(chuàng)造更易維護(hù)、拓展性強(qiáng)、伸縮性強(qiáng)和更易測(cè)試的代碼。


【翻譯】SOLID原則:提升Python中的面向?qū)ο笤O(shè)計(jì)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
施秉县| 繁昌县| 新乐市| 离岛区| 皮山县| 寿宁县| 临朐县| 淳化县| 增城市| 芜湖市| 镇坪县| 呼图壁县| 景谷| 甘泉县| 凤阳县| 马边| 民县| 防城港市| 龙山县| 获嘉县| 林芝县| 西充县| 安丘市| 栾城县| 囊谦县| 彝良县| 建平县| 汉阴县| 会同县| 深州市| 会东县| 秭归县| 科技| 安国市| 蒙阴县| 湄潭县| 呼伦贝尔市| 宁强县| 江津市| 尼勒克县| 安宁市|