認識 SOLID 原則
無論是軟體系統設計,還是代碼實作,遵循有效和明確的設計原則,都利于系統軟體靈活可靠,安全快速的落地,更重要的是能靈活地應對需求,簡化系統擴展和維護,避免無效的加班,本文主要討論面向物件軟體開發中最流行的設計原則- SOLID,它是五個設計原則為了方便記憶而組成的首字母縮寫:
- 單一職責原則
- 開放/封閉原則
- 里式替換原則
- 介面隔離原則
- 依賴倒置原則
S:單一職責原則 (SRP)
基本概念
單一職責原則 (SRP) 英文全稱為 Single Responsibility Principle,是最簡單,但也是最難用好的原則之一,它的定義也很簡單:對于一個類而言,應該僅有一個引起它變化的原因,其中變化的原因就表示了這個類的職責,它可能是某個特定領域的功能,可能是某個需求的解決方案,
這個原則表達的是不要讓一個類承擔過多的責任,一旦有了多個職責,那么它就越容易因為某個職責而被更改,這樣的狀態是不穩定的,不經意的修改很有可能影響到這個類的其他功能,因此,我們需要將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,不同類之間的變化互不影響,
實體說明
舉一個具體的例子,有一個用于實作編輯和列印報表的類,這樣的類存在兩個變化的原因:第一,報表的內容可以改變(編輯),第二,報表的格式可以改變(列印),如果有一個對于報表編輯流程的修改,而報表的編輯流程會導致公共狀態或者依賴關系的改變,使得列印功能的代碼無法作業,所以單一職責原則認為這兩個變化的原因事實上是兩個分離的功能,它們應該分離在不同的類中,
相關設計模式
面對違背單一職責原則的程式代碼,我們可以利用外觀模式,代理模式,橋接模式,配接器模式,命令模式對已有設計進行重構,實作多職責的分離,
小結
單一職責原則用于控制類的粒度大小,減少類中不相關功能的代碼耦合,使得類更加的健壯;另外,單一職責原則也適用于模塊之間解耦,對于模塊的功能劃分有很大的指導意義,
O:開閉原則 (OCP)
基本概念
開閉原則 (OCP) 英文全稱為 Open-Closed Principle,基本定義是軟體中的物件(類,模塊,函式等)應該對于擴展是開放的,但是對于修改是封閉的,這里的對擴展開放表示這添加新的代碼,就可以讓程式行為擴展來滿足需求的變化;對修改封閉表示在擴展程式行為時不要修改已有的代碼,進而避免影響原有的功能,
要實作不改代碼的情況下,仍要去改變系統行為的關鍵就是抽象和多型,通過介面或者抽象類定義系統的抽象層,再通過具體類來進行擴展,這樣一來,無須對抽象層進行任何改動,只需要增加新的具體類來實作新的業務功能即可,達到開閉原則的要求,
實體說明
同樣,舉個例子來更深刻地理解開閉原則:有一個用于圖表顯示的 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 物件即可,無須修改現有類別庫的源代碼,
相關設計模式
面對違背開閉原則的程式代碼,可以用到的設計模式有很多,比如工廠模式,觀察者模式,模板方法模式,策略模式,組合模式,使用相關設計模式的關鍵點就是識別出最有可能變化和擴展的部分,然后構造抽象來隔離這些變化,
小結
有了開閉原則,面向需求的變化就能進行快速的調整實作功能,這大大提高系統的靈活性,可重用性和可維護性,但會增加一定的復雜性,
L: 里式替換原則 (LSP)
基本概念
里式替換原則 (LSP) 英文全稱為 Liskov Substitution Principle,基本定義為:在不影響程式正確性的基礎上,所有使用基類的地方都能使用其子類的物件來替換,這里提到的基類和子類說的就是具有繼承關系的兩類物件,當我們傳遞一個子型別物件時,需要保證程式不會改變任何原基類的行為和狀態,程式能正常運作,
實體說明
為了能理解里式替換原則,這里舉一個經典的違反里式替換原則的例子:正方形/長方形問題,
上圖為正方形/長方形問題的類層次結構,Square 類繼承了 Rectangle 類,但是 Rectangle 類的寬高可以分別修改,但是 Suqare 類的寬高則必須一同修改,如果 User 類操作 Rectangle 類時,但實際物件是 Suqare 型別時,就會造成程式的出錯,如下方代碼:
Rectangle r = ...; // 回傳具體型別物件
r.setWidth(5);
r.setHeight(2);
assert(r.area() == 10);
當回傳具體型別物件為 Suqare 型別,面積為 10 的斷言就是失敗,這樣明顯是不符合里式替換原則的,
小結
要讓程式代碼符合里式替換原則,需要保證子類繼承父類時,除添加新的方法完成新增功能外,盡量不要重寫父類的方法,換句話就是子類可以擴展父類的功能,但不能改變父類原有的功能,
另一方面,里式替換原則也是對開閉原則的補充,不僅適用于繼承關系,還適用于實作關系的設計,常提到的 IS-A 關系是針對行為方式來說的,如果兩個類的行為方式是不相容,那么就不應該使用繼承,更好的方式是提取公共部分的方法來代替繼承,
I:介面隔離原則 (ISP)
基本概念
介面隔離原則 (ISP) 英文全稱為 Interface Segregation Principle,基本定義:客戶端不應該依賴那些它不需要的介面,客戶端應該只依賴它實際使用的方法,因為如果一個介面具備了若干個方法,那就意味著它的實作類都要實作所有介面方法,從代碼結構上就十分臃腫,
實體說明

現在我們看下一個違反介面隔離原則的例子,從上面類結構圖中,有多個用戶需要操作 Operation 類,如果 User1 只需要使用 operation1 方法,User2 只需要使用 operation2 方法,User3 只需要使用 operation3 方法,那么很明顯對于 User1 來說,不應該看到 operation2 和 operation3 這兩個方法,要減少對自己不關心的方法的依賴,防止 Operation 類中 operation2 和 operation3 方法的修改,影響到 User1 的功能,這個問題可以通過將不同的操作隔離成獨立的介面來解決,具體如下圖所示,
基于介面隔離原則,我們需要做的就是減少定義大而全的介面,類所要實作的介面應該分解成多個介面,然后根據所需要的功能去實作,并且在使用到介面方法的地方,用對應的介面型別去宣告,這樣可以解除呼叫方與物件非相關方法的依賴關系,總結一下,介面隔離原則主要功能就是控制介面的粒度大小,防止暴露給客戶端無相關的代碼和方法,保證了介面的高內聚,降低與客戶端的耦合,
D:依賴倒置原則 (DIP)
基本概念
依賴倒置原則 (DIP) 英文全稱 Dependency Inversion Principle, DIP),基本定義是:
- 高層模塊不應該依賴低層模塊,應該共同依賴抽象;
- 抽象不應該依賴細節,細節應該依賴抽象,
這里的抽象就是介面和抽象類,而細節就是實作介面或繼承抽象類而產生的類,
實體說明
如何理解“高層模塊不應該依賴低層模塊,應該共同依賴抽象”呢?如果高層模塊依賴于低層模塊,那么低層模塊的改動很有可能影響到高層模塊,從而導致高層模塊被迫改動,這樣一來讓高層模塊的重用變得非常困難,
而最佳的做法就如上圖一樣,在高層模塊構建一個穩定的抽象層,并且只依賴這個抽象層;而由底層模塊完成抽象層的實作細節,這樣一來,高層類都通過該抽象介面使用下一層,移除了高層對底層實作細節的依賴,
相關設計模式
關于依賴倒置原則,可以用到的設計模式有工廠模式,模板方法模式,策略模式,
小結
依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,降低并行開發引起的風險,提高代碼的可讀性和可維護性,同時依賴倒置原則也是框架設計的核心原則,善于創建可重用的框架和富有擴展性的代碼,比如 Tomcat 容器的 Servlet 規范實作,Spring Ioc 容器實作,
結語
到這里,SOLID 設計原則就全部介紹完了,本文的主要目的還是對這六項原則系統地整理和總結,在后續的程式設計開發程序中能有意識地識別出設計原則和模式,如果大家對設計原則有更多想法和理解,歡迎留言,大家共同探討,

本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/149313.html
標籤:Java
