工作那么久,才知道的 SOLID 設計原則

<a name="ROgDO"></a>

認識 SOLID 原則

無論是軟件系統設計,還是代碼實現,遵循有效和明確的設計原則,都利于系統軟件靈活可靠,安全快速的落地,更重要的是能靈活地應對需求,簡化系統擴展和維護,避免無效的加班。本文主要討論面向對象軟件開發中最流行的設計原則- SOLID,它是五個設計原則為了方便記憶而組成的首字母縮寫:

  • 單一職責原則
  • 開放/封閉原則
  • 里式替換原則
  • 接口隔離原則
  • 依賴倒置原則

<a name="y1C37"></a>

S:單一職責原則 (SRP)

<a name="0cpdR"></a>

基本概念

單一職責原則 (SRP) 英文全稱為 Single Responsibility Principle,是最簡單,但也是最難用好的原則之一。它的定義也很簡單:對于一個類而言,應該僅有一個引起它變化的原因。其中變化的原因就表示了這個類的職責,它可能是某個特定領域的功能,可能是某個需求的解決方案。<br />
<br />這個原則表達的是不要讓一個類承擔過多的責任,一旦有了多個職責,那么它就越容易因為某個職責而被更改,這樣的狀態是不穩定的,不經意的修改很有可能影響到這個類的其他功能。因此,我們需要將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,不同類之間的變化互不影響。

<a name="fPviV"></a>

實例說明

舉一個具體的例子,有一個用于實現編輯和打印報表的類,這樣的類存在兩個變化的原因:第一,報表的內容可以改變(編輯)。第二,報表的格式可以改變(打印)。如果有一個對于報表編輯流程的修改,而報表的編輯流程會導致公共狀態或者依賴關系的改變,使得打印功能的代碼無法工作。所以單一職責原則認為這兩個變化的原因事實上是兩個分離的功能,它們應該分離在不同的類中。<br />
image.png

<br />

<a name="nc3XW"></a>

相關設計模式

面對違背單一職責原則的程序代碼,我們可以利用外觀模式,代理模式,橋接模式,適配器模式,命令模式對已有設計進行重構,實現多職責的分離。<br />

<a name="1aXvA"></a>

小結

單一職責原則用于控制類的粒度大小,減少類中不相關功能的代碼耦合,使得類更加的健壯;另外,單一職責原則也適用于模塊之間解耦,對于模塊的功能劃分有很大的指導意義。<br />

<a name="2sOY4"></a>

O:開閉原則 (OCP)

<a name="IfnpC"></a>

基本概念

開閉原則 (OCP) 英文全稱為 Open-Closed Principle,基本定義是軟件中的對象(類,模塊,函數等)應該對于擴展是開放的,但是對于修改是封閉的。這里的對擴展開放表示這添加新的代碼,就可以讓程序行為擴展來滿足需求的變化;對修改封閉表示在擴展程序行為時不要修改已有的代碼,進而避免影響原有的功能。<br />
<br />要實現不改代碼的情況下,仍要去改變系統行為的關鍵就是抽象和多態,通過接口或者抽象類定義系統的抽象層,再通過具體類來進行擴展。這樣一來,無須對抽象層進行任何改動,只需要增加新的具體類來實現新的業務功能即可,達到開閉原則的要求。

<a name="tgDLP"></a>

實例說明

同樣,舉個例子來更深刻地理解開閉原則:有一個用于圖表顯示的 Display 類,它能繪制各種類型的圖表,比如餅狀圖,柱狀圖等;而需要繪制特定圖表時,都強依賴了對應類型的圖表,Display 類的內部實現如下:

public void display(String type) {
    if (type.equals("pie")) {  
      PieChart chart = new PieChart();  
      chart.display();  
    }  else if (type.equals("bar")) {  
      BarChart chart = new BarChart();  
      chart.display();  
    } 
}

基于上述的代碼,如果需要新增一個圖表,比如折線圖 LineChart ,就要修改 Display 類的 display() 方法,增加新增的判斷邏輯,很顯然這樣的做法違反開閉原則。而讓類的實現符合開閉原則的方式就是引入抽象圖表類 AbstractChart,作為其他圖表的基類,讓 Display 依賴這個抽象圖表類 AbstractChart,然后通過 Display 決定使用哪種具體的圖表類,實現代碼變成了這樣:

private Abstractchart chart;

public void display() {
    chart.display();  
}

現在我們需要新增折線圖顯示,在客戶端向 Display 中注入一個 LineChart 對象即可,無須修改現有類庫的源代碼。<br />
image.png

<br />

<a name="OnWvd"></a>

相關設計模式

面對違背開閉原則的程序代碼,可以用到的設計模式有很多,比如工廠模式,觀察者模式,模板方法模式,策略模式,組合模式,使用相關設計模式的關鍵點就是識別出最有可能變化和擴展的部分,然后構造抽象來隔離這些變化。<br />

<a name="UXUof"></a>

小結

有了開閉原則,面向需求的變化就能進行快速的調整實現功能,這大大提高系統的靈活性,可重用性和可維護性,但會增加一定的復雜性。<br />

<a name="FTxUH"></a>

L: 里式替換原則 (LSP)

<a name="lEvOy"></a>

基本概念

里式替換原則 (LSP) 英文全稱為 Liskov Substitution Principle,基本定義為:在不影響程序正確性的基礎上,所有使用基類的地方都能使用其子類的對象來替換。這里提到的基類和子類說的就是具有繼承關系的兩類對象,當我們傳遞一個子類型對象時,需要保證程序不會改變任何原基類的行為和狀態,程序能正常運作。<br />

<a name="OEq46"></a>

實例說明

為了能理解里式替換原則,這里舉一個經典的違反里式替換原則的例子:正方形/長方形問題。<br />
image.png

<br />上圖為正方形/長方形問題的類層次結構,Square 類繼承了 Rectangle 類,但是 Rectangle 類的寬高可以分別修改,但是 Suqare 類的寬高則必須一同修改。如果 User 類操作 Rectangle 類時,但實際對象是 Suqare 類型時,就會造成程序的出錯,如下方代碼:

Rectangle r = ...; // 返回具體類型對象
r.setWidth(5);
r.setHeight(2);
assert(r.area() == 10);

當返回具體類型對象為 Suqare 類型,面積為 10 的斷言就是失敗,這樣明顯是不符合里式替換原則的。

<a name="fumSX"></a>

小結

要讓程序代碼符合里式替換原則,需要保證子類繼承父類時,除添加新的方法完成新增功能外,盡量不要重寫父類的方法,換句話就是子類可以擴展父類的功能,但不能改變父類原有的功能。<br />
<br />另一方面,里式替換原則也是對開閉原則的補充,不僅適用于繼承關系,還適用于實現關系的設計,常提到的 IS-A 關系是針對行為方式來說的,如果兩個類的行為方式是不相容,那么就不應該使用繼承,更好的方式是提取公共部分的方法來代替繼承。<br />

<a name="ObaeQ"></a>

I:接口隔離原則 (ISP)

<a name="0XSSh"></a>

基本概念

接口隔離原則 (ISP) 英文全稱為 Interface Segregation Principle,基本定義:客戶端不應該依賴那些它不需要的接口。客戶端應該只依賴它實際使用的方法,因為如果一個接口具備了若干個方法,那就意味著它的實現類都要實現所有接口方法,從代碼結構上就十分臃腫。<br />

<a name="eDZXC"></a>

實例說明

<br />
image.png

<br />

<br />現在我們看下一個違反接口隔離原則的例子,從上面類結構圖中,有多個用戶需要操作 Operation 類。如果 User1 只需要使用 operation1 方法,User2 只需要使用 operation2 方法,User3 只需要使用 operation3 方法,那么很明顯對于 User1 來說,不應該看到 operation2 和 operation3 這兩個方法,要減少對自己不關心的方法的依賴,防止 Operation 類中 operation2 和 operation3 方法的修改,影響到 User1 的功能。這個問題可以通過將不同的操作隔離成獨立的接口來解決,具體如下圖所示。<br />
image.png
<br />
<br />

<br />
<br />基于接口隔離原則,我們需要做的就是減少定義大而全的接口,類所要實現的接口應該分解成多個接口,然后根據所需要的功能去實現,并且在使用到接口方法的地方,用對應的接口類型去聲明,這樣可以解除調用方與對象非相關方法的依賴關系。總結一下,接口隔離原則主要功能就是控制接口的粒度大小,防止暴露給客戶端無相關的代碼和方法,保證了接口的高內聚,降低與客戶端的耦合。

<a name="EbOPA"></a>

D:依賴倒置原則 (DIP)

<a name="XF76Z"></a>

基本概念

依賴倒置原則 (DIP) 英文全稱 Dependency Inversion Principle, DIP),基本定義是:

  • 高層模塊不應該依賴低層模塊,應該共同依賴抽象;
  • 抽象不應該依賴細節,細節應該依賴抽象。

這里的抽象就是接口和抽象類,而細節就是實現接口或繼承抽象類而產生的類。<br />

<a name="186Ix"></a>

實例說明

如何理解“高層模塊不應該依賴低層模塊,應該共同依賴抽象”呢?如果高層模塊依賴于低層模塊,那么低層模塊的改動很有可能影響到高層模塊,從而導致高層模塊被迫改動,這樣一來讓高層模塊的重用變得非常困難。


file

而最佳的做法就如上圖一樣,在高層模塊構建一個穩定的抽象層,并且只依賴這個抽象層;而由底層模塊完成抽象層的實現細節。這樣一來,高層類都通過該抽象接口使用下一層,移除了高層對底層實現細節的依賴。

<a name="4cfgn"></a>

相關設計模式

關于依賴倒置原則,可以用到的設計模式有工廠模式,模板方法模式,策略模式。

<a name="H6Sib"></a>

小結

依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,降低并行開發引起的風險,提高代碼的可讀性和可維護性。同時依賴倒置原則也是框架設計的核心原則,善于創建可重用的框架和富有擴展性的代碼,比如 Tomcat 容器的 Servlet 規范實現,Spring Ioc 容器實現。<br />

<a name="gNrWD"></a>

結語

到這里,SOLID 設計原則就全部介紹完了,本文的主要目的還是對這六項原則系統地整理和總結,在后續的程序設計開發過程中能有意識地識別出設計原則和模式。如果大家對設計原則有更多想法和理解,歡迎留言,大家共同探討。<br />

本文由博客一文多發平臺 OpenWrite 發布!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374