不管是軟件系統設計,仍是代碼實現,遵循有效和明確的設計原則,都利於系統軟件靈活可靠,安全快速的落地,更重要的是能靈活地應對需求,簡化系統擴展和維護,避免無效的加班。本文主要討論面向對象軟件開發中最流行的設計原則- SOLID,它是五個設計原則爲了方便記憶而組成的首字母縮寫:java
單一職責原則 (SRP) 英文全稱爲 Single Responsibility Principle,是最簡單,但也是最難用好的原則之一。它的定義也很簡單:對於一個類而言,應該僅有一個引發它變化的緣由。其中變化的緣由就表示了這個類的職責,它多是某個特定領域的功能,多是某個需求的解決方案。
這個原則表達的是不要讓一個類承擔過多的責任,一旦有了多個職責,那麼它就越容易由於某個職責而被更改,這樣的狀態是不穩定的,不經意的修改頗有可能影響到這個類的其餘功能。所以,咱們須要將不一樣的職責封裝在不一樣的類中,即將不一樣的變化緣由封裝在不一樣的類中,不一樣類之間的變化互不影響。設計模式
舉一個具體的例子,有一個用於實現編輯和打印報表的類,這樣的類存在兩個變化的緣由:第一,報表的內容能夠改變(編輯)。第二,報表的格式能夠改變(打印)。若是有一個對於報表編輯流程的修改,而報表的編輯流程會致使公共狀態或者依賴關係的改變,使得打印功能的代碼沒法工做。因此單一職責原則認爲這兩個變化的緣由事實上是兩個分離的功能,它們應該分離在不一樣的類中。
安全
面對違背單一職責原則的程序代碼,咱們能夠利用外觀模式,代理模式,橋接模式,適配器模式,命令模式對已有設計進行重構,實現多職責的分離。
框架
單一職責原則用於控制類的粒度大小,減小類中不相關功能的代碼耦合,使得類更加的健壯;另外,單一職責原則也適用於模塊之間解耦,對於模塊的功能劃分有很大的指導意義。
函數
開閉原則 (OCP) 英文全稱爲 Open-Closed Principle,基本定義是軟件中的對象(類,模塊,函數等)應該對於擴展是開放的,可是對於修改是封閉的。這裏的對擴展開放表示這添加新的代碼,就可讓程序行爲擴展來知足需求的變化;對修改封閉表示在擴展程序行爲時不要修改已有的代碼,進而避免影響原有的功能。
要實現不改代碼的狀況下,仍要去改變系統行爲的關鍵就是抽象和多態,經過接口或者抽象類定義系統的抽象層,再經過具體類來進行擴展。這樣一來,無須對抽象層進行任何改動,只須要增長新的具體類來實現新的業務功能便可,達到開閉原則的要求。spa
一樣,舉個例子來更深入地理解開閉原則:有一個用於圖表顯示的 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 決定使用哪一種具體的圖表類,實現代碼變成了這樣:3d
private Abstractchart chart; public void display() { chart.display(); }
如今咱們須要新增折線圖顯示,在客戶端向 Display 中注入一個 LineChart 對象便可,無須修改現有類庫的源代碼。
代理
面對違背開閉原則的程序代碼,能夠用到的設計模式有不少,好比工廠模式,觀察者模式,模板方法模式,策略模式,組合模式,使用相關設計模式的關鍵點就是識別出最有可能變化和擴展的部分,而後構造抽象來隔離這些變化。
code
有了開閉原則,面向需求的變化就能進行快速的調整實現功能,這大大提升系統的靈活性,可重用性和可維護性,但會增長必定的複雜性。
裏式替換原則 (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 關係是針對行爲方式來講的,若是兩個類的行爲方式是不相容,那麼就不該該使用繼承,更好的方式是提取公共部分的方法來代替繼承。
接口隔離原則 (ISP) 英文全稱爲 Interface Segregation Principle,基本定義:客戶端不該該依賴那些它不須要的接口。客戶端應該只依賴它實際使用的方法,由於若是一個接口具有了若干個方法,那就意味着它的實現類都要實現全部接口方法,從代碼結構上就十分臃腫。
如今咱們看下一個違反接口隔離原則的例子,從上面類結構圖中,有多個用戶須要操做 Operation 類。若是 User1 只須要使用 operation1 方法,User2 只須要使用 operation2 方法,User3 只須要使用 operation3 方法,那麼很明顯對於 User1 來講,不該該看到 operation2 和 operation3 這兩個方法,要減小對本身不關心的方法的依賴,防止 Operation 類中 operation2 和 operation3 方法的修改,影響到 User1 的功能。這個問題能夠經過將不一樣的操做隔離成獨立的接口來解決,具體以下圖所示。
基於接口隔離原則,咱們須要作的就是減小定義大而全的接口,類所要實現的接口應該分解成多個接口,而後根據所須要的功能去實現,而且在使用到接口方法的地方,用對應的接口類型去聲明,這樣能夠解除調用方與對象非相關方法的依賴關係。總結一下,接口隔離原則主要功能就是控制接口的粒度大小,防止暴露給客戶端無相關的代碼和方法,保證了接口的高內聚,下降與客戶端的耦合。
依賴倒置原則 (DIP) 英文全稱 Dependency Inversion Principle, DIP),基本定義是:
這裏的抽象就是接口和抽象類,而細節就是實現接口或繼承抽象類而產生的類。
如何理解「高層模塊不該該依賴低層模塊,應該共同依賴抽象」呢?若是高層模塊依賴於低層模塊,那麼低層模塊的改動頗有可能影響到高層模塊,從而致使高層模塊被迫改動,這樣一來讓高層模塊的重用變得很是困難。
而最佳的作法就如上圖同樣,在高層模塊構建一個穩定的抽象層,而且只依賴這個抽象層;而由底層模塊完成抽象層的實現細節。這樣一來,高層類都經過該抽象接口使用下一層,移除了高層對底層實現細節的依賴。
關於依賴倒置原則,能夠用到的設計模式有工廠模式,模板方法模式,策略模式。
依賴倒置原則能夠減小類間的耦合性,提升系統的穩定性,下降並行開發引發的風險,提升代碼的可讀性和可維護性。同時依賴倒置原則也是框架設計的核心原則,善於建立可重用的框架和富有擴展性的代碼,好比 Tomcat 容器的 Servlet 規範實現,Spring Ioc 容器實現。
到這裏,SOLID 設計原則就所有介紹完了,本文的主要目的仍是對這六項原則系統地整理和總結,在後續的程序設計開發過程當中能有意識地識別出設計原則和模式。若是你們對設計原則有更多想法和理解,歡迎留言,你們共同探討。