面向物件的編程并不能防止難以理解或不可維護的程式,因此,Robert C. Martin 制定了五項指導原則,使開發人員很容易創建出可讀性強且可維護的程式,這五項原則被稱為 S.O.L.I.D 原則,
面向物件編程帶來了新的軟體開發設計方法,它使得開發人員能夠將具有相同作用 / 功能的資料組合到一個類中,實作唯一的目的,而不管整個應用程式如何,
但是,這種面向物件的編程并不能防止難以理解或不可維護的程式,因此,Robert C. Martin 制定了五項指導原則,使開發人員很容易創建出可讀性強且可維護的程式,這五項原則被稱為 S.O.L.I.D 原則(這種縮寫是由 Michael Feathers 提出的):
S:單一職責原則
O:開閉原則
L:里氏替換原則
I:介面隔離原則
D:依賴倒置原則
下面我們將展開詳細的討論,
注意:本文中的大多數示例可能不能滿足實際情況或不能應用于實際的應用程式,這完全取決于你自己的設計和場景,最重要的是理解并知道如何應用 / 遵循這些原則,
提示:SOLID 原則是為構建模塊化、可擴展和可組合的封裝組件而設計的,Bit 是實踐這一原則的一個強大的工具:它能夠幫助你在不同的專案中、在團隊范圍內輕松地隔離、共享和管理這些組件,
單一職責原則(SRP)
一個類只應該負責一件事,如果一個類有多個職責,那么它變成了耦合的,對一個職責的修改會導致對另一個職責的修改,
注意:這個原則不僅適用于類,也適用于軟體組件和微服務,
例如,考慮下面的設計:
上面的 Animal 就違反了單一職責原則(SRP),
它為什么違反了 SRP?
SRP 指出,類應該有一個職責,在這里,我們可以得出兩個職責:動物資料庫管理和動物屬性管理,建構式和 getAnimalName 管理動物屬性,而 saveAnimal 管理 Animal 在資料庫中的存盤,
這種設計將來會帶來什么問題?
如果應用程式的修改影響了資料庫管理功能,使用 Animal 屬性的類就必須修改和重新編譯,以適應這種新的變化,這個系統就有點像多米諾骨牌,觸碰一張牌就會影響到其他牌,
為了使這個類符合 SRP,我們創建了另一個類,它負責將動物存盤到資料庫中這個單獨的職責:
在設計我們的類時,我們應該把相關的特性放在一起,這樣,每當它們需要改變的時候,它們都是因為同樣的原因而改變,如果它們因不同的原因而改變,我們就應該嘗試將它們分開,——Steve Fenton
恰當運用這條原則,我們的應用程式就會變成高內聚的,
開閉原則(OCP)
軟體物體(類、模塊、函式)應該對擴展開放,對修改關閉,
讓我們繼續以 Animal 類為例,
我們希望遍歷一個動物串列,發出它們的聲音,
函式 AnimalSound 不符合開閉原則,因為它不能對新的動物關閉,如果我們添加一種新的動物蛇:
我們就不得不修改 AnimalSound 函式:
如你所見,對于每一種新的動物,一段新的邏輯會被添加到 AnimalSound 函式,這是一個非常簡單的例子,當應用程式變得龐大而復雜時,你會看到,每添加一種新動物,if 陳述句就得在 AnimalSound 函式中重復一遍,
如何使它(AnimalSound)符合 OCP?
Animal 現在有了一個虛方法 makeSound,我們讓每一種動物擴展 Animal 類并實作 makeSound 方法,
每一種動物都加入自己的發聲方法(makeSound)實作,AnimalSound 遍歷動物陣列并呼叫每種動物的 makeSound 方法,
現在,如果我們添加一種新動物,AnimalSound 不需要修改,我們需要做的就是把新動物加入到動物陣列中,
AnimalSound 方法符合 OCP 原則了,
再舉個例子,假如你有一家商店,你使用下面的類給自己最喜歡的客戶 20% 的折扣:
當你決定給 VIP 客戶雙倍的折扣(40%)時,你可能會這樣修改這個類:
這就違反了 OCP 原則,OCP 禁止這樣做,如果想給不同型別的客戶一個新的折扣百分比,就得添加一段新的邏輯,
為了使它遵循 OCP 原則,我們將新建一個類來擴展 Discount,在這個新類中,我們將重新實作它的行為:
如果你決定給超級 VIP 客戶 80% 的折扣,那么代碼是下面這個樣子:
就是這樣,擴展而不修改,
里氏替換原則(LSP)
子類必須可以替換它的超類,
這個原則的目的是確保子類可以替換它的超類而沒有錯誤,如果你發現自己的代碼在檢查類的型別,那么它一定違反了這個原則,
讓我們以 Animal 為例,
上述方法違反了 LSP 原則(也違反了 OCP 原則),它必須知道每一種 Animal 型別,并呼叫相應的數腿函式,
每次創建一個新的動物類,都得修改這個函式:
為了使這個函式符合 LSP 原則,我們將遵循 Steve Fenton 提出的 LSP 要求:
如果超類(Animal)有一個方法接受超型別別(Anima)的引數,那么它的子類(Pigeon)應該接受超型別別(Animal 型別)或子型別別(Pigeon 型別)作為引數,
如果超類回傳一個超型別別(Animal), 那么它的子類應該回傳一個超型別別(Animal 型別)或子型別別(Pigeon 型別),
現在,我們可以重新實作 AnimalLegCount 函式了:
AnimalLegCount 函式并不關心傳遞的動物型別,它只管呼叫 LegCount 方法,它只知道引數必須是 Animal 型別,要么是 Animal 類,要么是它的子類,
現在,Animal 類必須實作 / 定義一個 LegCount 方法:
而它的子類必須實作 LegCount 方法:
當它被傳遞給 AnimalLegCount 函式時,它會回傳一頭獅子的腿數,
如你所見,AnimalLegCount 不需要知道動物的型別就可以回傳它的腿數,它只呼叫了 Animal 型別的 LegCount 方法,因為根據約定,Animal 類的一個子類必須實作 LegCount 函式,
介面隔離原則(ISP)
創建特定于客戶端的細粒度介面,不應該強迫客戶端依賴于它們不使用的介面,
這個原則是為了克服實作大介面的缺點,讓我們看看下面的 IShape 介面:
這個介面可以繪制正方形、圓形、矩形,實作 IShape 介面的類 Circle、Square 和 Rectangle 必須定義方法 drawCircle()、drawSquare()、drawRectangle(),
上面的代碼很有趣,類 Rectangle 實作了它沒有使用的方法 drawCircle 和 drawSquare,同樣,Square 實作了 drawCircle 和 drawRectangle,Circle 實作了 drawSquare 和 drawRectangle,
如果我們向 IShape 介面添加另一個方法,比如 drawTriangle():
那么,這些類就必須實作新方法,否則就會拋出錯誤,
我們看到,不可能實作這樣一種形狀類,它可以畫圓,但不能畫矩形、正方形或三角形,我們在實作方法時可以只拋出一個錯誤,表明操作無法執行,
ISP 反對 IShape 介面的這種設計,客戶端(這里是 Rectangle、Circle 和 Square)不應該被迫依賴于它們不需要或不使用的方法,另外,ISP 指出,介面應該只執行一個任務(就像 SRP 原則一樣),任何額外的行為都應該抽象到另一個介面中,
在這里,我們的 IShape 介面執行了應該由其他介面獨立處理的操作,為了使 IShape 介面符合 ISP 原則,我們將對不同介面的操作進行隔離:
ICircle 介面僅處理圓的繪制,IShape 處理任何形狀的繪制,ISquare 只處理正方形的繪制,IRectangle 處理矩形的繪制,
或者,類(Circle、Rectangle、Square、Triangle)必須繼承 IShape 介面,并實作自己的繪制行為,
然后,我們可以使用 I- 介面創建具體的形狀,如半圓、直角三角形、等邊三角形、鈍邊矩形等,
依賴倒置原則(DIP)
依賴應該是抽象的,而不是具體的,
高級模塊不應該依賴于低級模塊,兩者都應該依賴于抽象,
抽象不應該依賴于細節,細節應該依賴于抽象,
在軟體開發中,我們的應用程式最終主要是由模塊組成,當這種情況出現時,我們必須使用依賴注入來解決,高級組件依賴于低級組件發揮作用,
這里,Http 是高級組件,而 HttpService 是低級組件,這種設計違反了 DIP A:高級模塊不應該依賴于低級模塊,它應該依賴于它的抽象,
該 Http 類被迫依賴于 XMLHttpService 類,如果我們要修改 Http 連接服務,也許我們想通過 Nodejs 連接到互聯網,甚至模擬 http 服務,我們將不得不費力地遍歷所有 Http 實體來編輯代碼,這違反了 OCP 原則,
Http 類不應該關心使用的 Http 服務的型別,我們做了一個 Connection 介面:
Connection 介面有一個 request 方法,有了這個介面,我們就可以向 Http 類傳遞一個 Connection 型別的引數:
因此,無論傳遞給 Http 類的 Http 連接服務是什么型別,它都可以輕松地連接到網路,而無需知道網路連接的型別,
現在,我們重新實作 XMLHttpService 類來實作 Connection 介面:
我們可以創建許多 Http 連接型別,并將其傳遞給 Http 類,而不必擔心錯誤,
現在,我們可以看到,高級模塊和低級模塊都依賴于抽象,Http 類(高級模塊)依賴于 Connection 介面(抽象),而 Http 服務型別(低級模塊)也依賴于 Connection 介面(抽象),
此外,DIP 原則會強制我們遵循里氏替換原則:Connection 型別 Node-XML-MockHttpService 可以替換它們的父型別連接,
小結
本文介紹了每個軟體開發人員都必須遵守的五個原則,首先,要遵守所有這些原則可能會令人生畏,但是隨著不斷的實踐和堅持,它們會成為我們的一部分,并將對應用程式的維護產生巨大的影響,
關于這些原則,如果你覺得有什么需要添加、糾正或洗掉,請在下面的評論區留言,我非常樂意與你討論!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/88162.html
標籤:其他
上一篇:雙活資料中心架構分析及優缺點
