架構設計的五大原則-SOLID

咖啡拿鐵發表於2018-10-25

1.背景

最近在讀《架構整潔之道》這一本書,這本書的確寫得不錯,最近也沒有更新文章,一方面再忙工作,另一方面也再啃一些書。當然文章還是得更新,《架構整潔之道》裡面有些有意思的內容我會提取出來外加自己的思考。在這本書裡面的第三章介紹了設計原則,這部分我覺得對於大家的平時工作都比較有用。

2. 設計原則

想必大家在學習物件導向的時候,都學習過下面幾大原則:

  • SRP 單一職責:該設計原則是基於康威定律的推論,每個軟體模組有且只有一個被更改的理由。

  • OCP 開閉原則:對擴充套件開放,對修改關閉。

  • LSP 里氏替換原則:任何基類可以出現的地方,子類一定可以出現。

  • ISP 介面隔離原則:在設計中需要避免不需要的依賴。

  • DIP 依賴反轉原則:高層策略性程式碼不應該依賴底層細節的程式碼,而應該是底層細節程式碼依賴高層策略。

這五個原則也被稱為,SOLID原則取的是他們的首字母。這個也是我們做一個好設計的基礎,接下來會依次對其進行解釋。

3.SRP:單一職責

SRP很容易被大家從字面意思無界,並不是每個模組只做一個事,而是每個模組的變化原因只有一個。在書中對於SRP最後的解釋是:

任何一個軟體模組都應該只對某一類行為者(有共同需求的人)負責。

這裡的軟體模組指的就是一個原始碼檔案或者一組緊密相關的函式和資料結構。SRP原則應該是大家運用得最多的原則之一。在書中舉了一個例子,有一個Employee類其中有三個函式:

  • calculatePay():計算工資,由財務部門制定,需要向CFO彙報。

  • reportHours():計算工時,人力資源制定,向COO彙報

  • save():由DBA制定,向CTO彙報。

這裡三個函式都放在了Employee類中,其實也就是把三個行為者的行為都耦合在了一起。一般來說計算工資,會獲取正常工時,而計算工時也會獲取工時,這兩個函式都依賴了一個獲取工時的方法,如果財務部門計算工資時,想修改邏輯,看大家辛苦了1個小時當1.1個小時發工資,這個時候修改了這個獲取工時的方法,但是HR部門並不需要這個修改,這個時候就會導致reportHours()這個方法出現資料錯誤。所以這個時候就需要將不同行為者的程式碼就行拆分。

3.1 如何解決

在書中給出了第一個解決方法:

設計出三個類,每個類都只與一個行為者相關。這種問題的壞處是,程式設計師需要在程式裡處理三個類,這裡還介紹了使用門面模式的方法,讓我們只需要在我們使用的地方使用一個類即可:

架構設計的五大原則-SOLID

這樣的話我們就不需要關心其他三個類,直接呼叫門面模式的方法即可。

3.2 實際場景

在實際場景中微服務可以算作是SRP的思想,雖然每一個微服務不止一個類,但是其整個服務也可以看做是一個模組,而每個一個模組基本也只於一個行為者相關。在我們的程式碼中可以使用3.1中所描述的方法來進行SRP的實現。

SRP的好處:

  • 修改程式碼容易,由於不需要考慮修改程式碼是否會影響其他業務所以是很容易的。

  • 更加容易維護,維護一個什麼邏輯都有的程式碼明顯比維護一個單一職責的程式碼難得多。

  • 容易發現問題,當出現問題的時候,由於職責清晰,可以比較容易的定位。

  • 松耦合,職責分離,耦合程度比較低。

4.OCP:開閉原則

在這本書中講述OCP可能和大家從一些資料上面看的有點不同。一般大家所認為的開閉原則,應該將那些容易變化的部分進行抽象,利用對抽象的多個實現來進行對擴充套件開放,而不是直接在類中去修改。

這裡我用吃飯的例子來列舉:

每個人一天都會吃三餐,早餐,午餐,晚餐,但是隨著時代的進步,又出現了下午茶,宵夜等,現在一天就不止三餐,那麼其實我們就需要在這個類中去新增喝下午茶方法,吃宵夜方法,這樣就導致我們沒增加一個餐的種類就需要新增一個方法,在將SRP的時候我們有個例子,在同一個類中修改方法的時候容易修改其他業務邏輯,在我們這個例子中我們也會出現這個問題。怎麼解決呢?那麼我們就可以將變化的部分抽象出來: 架構設計的五大原則-SOLID ,後續如果還需要增加吃的方法那麼只需要實現這個介面即可。

但是在這本書中,並沒有去強調將變化的部分抽象出來,其認為修改是不可避免的,所以我們需要把控好修改的影響,所以提出了高層元件的修改不會影響底層元件,元件層次越低越穩定。對於J2EE的開發者來說,三層開發肯定並不陌生,controller,service,dao:

如果我們修改controller那麼service其實是無感知的,不會受影響,如果我們修改service,dao是不會受影響的,但是我們的controller是會受影響。所以越底層的元件那麼其實應該越穩定。透過這種方式我們可以控制修改範圍的影響。總結起來就是透過將系統劃分為一系列元件,並且將這些元件間的依賴關係按層次結構進行組織,使得高階元件不會因低階元件被修改而受到影響。

5.LSP:里氏替換

任何基類可以出現的地方,子類一定可以出現。大多數人認為LSP其實就是指導如何使用繼承關係的一種方法,尤其是我們在開發的過程中用spring依賴注入的基本都是基類而非具體的實現類,這個的確也是LSP的一種實現手段。LSP也在逐漸演變成一種更廣泛的,指導介面與其實現方式的設計原則。

這裡用書中的一個反面例子來舉例,假如我們現在在構建一個提供計程車排程服務的系統,我們提供restful進行呼叫,有這麼一個司機,如果我們想排程他那麼需要訪問以下請求:

purplecab.com/driver/Bob/pickupAddress/24 Maple St./pickupTime/153/destination/ORD

每個公司想要接入我們的系統都得遵循上面的規矩,但是有一個公司Acme把destination寫成了縮寫dest,但是由於這個公司比較大,不想修改回來,所以排程系統只能寫如下的if邏輯:

if(driver.getDispatchUri().startsWith("acme.com"))

這種邏輯一般的軟體架構師都不會允許這樣一條語句出現在系統中,如果又出現了一個公司違反了那麼是否又需要增加一條if邏輯?軟體架構師應該建立一個排程請求建立元件,並讓該元件使用一個配置資料庫來儲存URI組裝格式,如下:

URI 排程格式
Acme.com /pickupAddress/%s/pickupTime/%s/dest/%s
. * . /pickupAddress/%s/pickupTime/%s/destination/%s

但是這樣我們也需要增加一個元件來應對這個情況,也增加了複雜性。

6.ISP:介面隔離原則

首先大家看看下面這個例子:

我們這裡的User1,User2,User3都是依賴OPS的,但是User1只需要用op1,User2用op2,User3用op3。在這種情況下,雖然User1不會和op2,op3產生直接的呼叫關係,但在原始碼層次上也與他們形成依賴關係。這種依賴關係會導致兩個問題:
  • 修改op2,op3的邏輯會導致op1的邏輯變化

  • 就算邏輯不變化,修改op2也會導致重新編譯和部署User1。

我們透過下面這種方將不同的操作隔離成介面,運用第5節的LSP,我們將OPS類實現這三個介面,然後替換在User1中的U1Ops,由於依賴的是最小介面所以就不會出現上面的問題。

架構設計的五大原則-SOLID

在書中對於ISP強調得比較多,在後面也講了CRP原則,不要強迫一個元件的使用者依賴他們不需要的東西,CRP是ISP的一個普適版。ISP是針對類來說,CRP是針對元件來說。所以我們總結起來就是:

不要依賴不需要的東西

7.DIP: 依賴反轉

依賴反轉其實總結起來就是多依賴抽象,少依賴具體實現。但是事事並沒有那麼絕對,我們的String類是一個具體的實現類但是在我們的程式碼中隨處可見,那是不是我們就違反了DIP了呢?其實不是的,我們String已經非常穩定了,就算修改也會被嚴格的控制,所以我們不需要擔心修改String類會發生一些意想不到的問題。所以對於我們穩定的東西,其實DIP原則就不適用了,而我們需要重點關注的應該經常變動的。這裡我想要說的一點的是,大家在編碼過程中寫List的時候雖然大多數時候用的是ArrayList,但是其實很少寫下面這句話ArrayList list = new ArrayList(),更多的是寫List list = new ArrayList(),其實這個就是DIP的一個實現。

這裡要說一下為什麼叫反轉?有反轉就有正轉,如下圖所示:

這裡的我們的serviceImpl是service的具體實現,在圖中我們的依賴流向沒有發生變化,所以叫正轉。我們採用DIP來進行設計:

可以看見我們這裡的最後的流向放生了變化,所以可以叫他依賴反轉。

DIP還有幾個編碼規則需要注意:

  • 多使用抽象介面,儘量避免使用具體實現類。

  • 儘量不要在具體實現類上面建立子類。

  • 儘量不要覆蓋繼承的抽象類的方法:由於我們依賴的是抽象,有可能邏輯中已經對這些方法產生了依賴,如果覆蓋有可能會造成問題。

8.總結

本文講了一下設計的五大原則SOLID,SOLID在這《架構整潔之道》中一直貫穿,這五大原則能幫助我們在設計的時候做出更多優秀的架構設計,如果想了解更多的一些細節可以看看這本書。

如果你覺得這篇文章對你有文章,可以關注我的技術公眾號,也可以加入我的技術交流群進行更多的技術交流。你的關注和轉發是對我最大的支援,O(∩_∩)O。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555607/viewspace-2217528/,如需轉載,請註明出處,否則將追究法律責任。

相關文章