<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 /><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 /><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 /><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 /><br />
<br />
<br />
<br />基于接口隔離原則,我們需要做的就是減少定義大而全的接口,類所要實現的接口應該分解成多個接口,然后根據所需要的功能去實現,并且在使用到接口方法的地方,用對應的接口類型去聲明,這樣可以解除調用方與對象非相關方法的依賴關系。總結一下,接口隔離原則主要功能就是控制接口的粒度大小,防止暴露給客戶端無相關的代碼和方法,保證了接口的高內聚,降低與客戶端的耦合。
<a name="EbOPA"></a>
D:依賴倒置原則 (DIP)
<a name="XF76Z"></a>
基本概念
依賴倒置原則 (DIP) 英文全稱 Dependency Inversion Principle, DIP),基本定義是:
- 高層模塊不應該依賴低層模塊,應該共同依賴抽象;
- 抽象不應該依賴細節,細節應該依賴抽象。
這里的抽象就是接口和抽象類,而細節就是實現接口或繼承抽象類而產生的類。<br />
<a name="186Ix"></a>
實例說明
如何理解“高層模塊不應該依賴低層模塊,應該共同依賴抽象”呢?如果高層模塊依賴于低層模塊,那么低層模塊的改動很有可能影響到高層模塊,從而導致高層模塊被迫改動,這樣一來讓高層模塊的重用變得非常困難。
而最佳的做法就如上圖一樣,在高層模塊構建一個穩定的抽象層,并且只依賴這個抽象層;而由底層模塊完成抽象層的實現細節。這樣一來,高層類都通過該抽象接口使用下一層,移除了高層對底層實現細節的依賴。
<a name="4cfgn"></a>
相關設計模式
關于依賴倒置原則,可以用到的設計模式有工廠模式,模板方法模式,策略模式。
<a name="H6Sib"></a>
小結
依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,降低并行開發引起的風險,提高代碼的可讀性和可維護性。同時依賴倒置原則也是框架設計的核心原則,善于創建可重用的框架和富有擴展性的代碼,比如 Tomcat 容器的 Servlet 規范實現,Spring Ioc 容器實現。<br />
<a name="gNrWD"></a>
結語
到這里,SOLID 設計原則就全部介紹完了,本文的主要目的還是對這六項原則系統地整理和總結,在后續的程序設計開發過程中能有意識地識別出設計原則和模式。如果大家對設計原則有更多想法和理解,歡迎留言,大家共同探討。<br />
本文由博客一文多發平臺 OpenWrite 發布!