歡迎光臨
每天分享高質量文章

設計樣式六大原則,你真的懂了嗎?

點擊上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

精品專欄

 

原文出自:https://www.cnblogs.com/1wen/

設計樣式不容易用文字描述清楚,而過多的代碼,看起來也讓人摸不到頭腦,加上詞語或者文字描述的抽象感,很容易讓人看了無數設計樣式的文章,也仍然理解不了。  所以我一直打算寫此系列博客,首先我會從大量文章里去理解這些設計樣式,最後我用自己的語言組織轉化為博客,希望用更少的代碼,更容易理解的文字,來聊一聊這些設計樣式。

我所理解、所描述的每一個設計樣式也可能有些是錯誤的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客內容。因為我是前端,所以設計樣式的代碼以前端代碼和視角為主。

此博客內容對每一種樣式並不會寫得非常深入,也許能為讀者打通一些認知,如果看了此系列博客,再去看其他更深入的博客,可能是一種比較好的方式。

單一職責原則

單一職責原則很簡單,一個方法 一個類只負責一個職責,各個職責的程式改動,不影響其它程式。  這是常識,幾乎所有程式員都會遵循這個原則。

里氏替換原則

一位姓里的女士提出來的,所以叫里氏替換原則。  通俗解釋此原則: 子類可以擴展父類的功能,但不能改變父類原有的功能。  它包含以下4層含義:

  • 子類可以實現父類的抽象方法,但不能改寫父類的非抽象方法

方法改寫又稱方法重寫,所以我理解這裡的改寫就是重寫吧。  當子類繼承了父類,有些情況下可能還是需要重寫繼承的方法。  但是重寫確實會給系統造成一些麻煩,特別是重寫的次數變多了之後,後期維護或者迭代的過程中容易概念混淆,邏輯混淆,加大犯錯的風險。父類的方法應該儘量穩定。

  • 子類中可以增加自己特有的方法

子類繼承父類,肯定是需要子類有自己特有方法的,否則就沒必要繼承了。

  • 當子類的方法多載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入引數更寬鬆。

輸入的引數更寬鬆可以理解為“更大的範圍或者定義”, 父類定義的入參要足夠寬泛,改寫子類需求,子類的引數應該在父類定義的範圍內,但是當父類方法不能滿足子類的情況下,出現多載或者重寫,這時候輸入的引數不放大的話,是沒法滿足業務需求的 。

有一個例子:鴕鳥不是鳥。  按照鳥的定義鴕鳥確實是鳥(恆溫動物,卵生,全身披有羽毛,身體呈流線形,有角質的喙,眼在頭的兩側。前肢退化成翼,後肢有鱗狀外皮,有四趾),那麼鴕鳥繼承於鳥這個基類,印象中鳥都是能飛的,所以鳥類定義一個飛行速度引數。  因為鴕鳥不能飛,就只能把速度定義為0。  現在出現一個業務,需要計算每個鳥類飛過黃河的時間,可是鴕鳥速度為0,時間永遠無法得出,這個就造成業務無法順利進行了,鴕鳥不能完全替代鳥,這也就違背了里氏替換原則。  所以這裡不能直接用鳥類來計算飛行時間,而是應該刪除鳥類的飛行速度引數,生成一個子類:飛鳥類,給飛鳥類定義飛行速度,去計算所有飛鳥類的飛行時間,這樣才能滿足需求。

需要註意兩點,1對類的繼承關係的定義要搞清楚,2設計要依賴於具體行為和環境。總之子類可以隨便擴展,但是別改父類。

  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的傳回值)要比父類更嚴格。

JS不需要定義抽象方法,傳回值也是不用申明的。  對於java來說,抽象方法只需要定義,方法體為空,當然也沒必要申明傳回,這個規則對java來說自然就已經遵循了。

依賴倒置原則

  • 高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象。

先說說什麼是高層模塊什麼是底層模塊。  高層模塊呼叫底層模塊,被另一個模塊呼叫的模塊就叫底層模塊。在傳統的應用架構中,低層次的組件設計用於被高層次的組件使用,這一點提供了逐步的構建一個複雜系統的可能。  在這種結構下,高層次的組件直接依賴於低層次的組件去實現一些任務,這種對於低層次組件的依賴限制了高層次組件被重用的可行性。  而依賴倒置原則使得高層次的模塊不依賴於低層次的模塊的實現細節,把高層次模塊從對低層次模塊的依賴中解耦出來,從而使得低層次模塊依賴於高層次模塊的需求抽象,當高層模塊需要使用底層模塊,便取用此底層模塊。  回想一下angular的依賴註入,就是這種。  下麵第二條的例子也可以幫助更好的理解。

  • 抽象不應該依賴於具體實現,具體實現應該依賴於抽象。

具體實現依賴於抽象,舉個例子,造一輛車,需要車架,輪子,沙發等。先搭好架子,定義一個具體實現類,安裝車架,放置沙發,安裝輪胎。具體實現流程已經具備,並且也定義好了抽象方法。

  1. Car(){

  2.     var body = new Body();

  3.     var tyre = new Tyre();

  4.     var sofa = new Sofa();

  5.     this.setBody(body);

  6.     this.setTyre(tyre);

  7.     this.setSofa(sofa);

  8.     return this;

  9. }

那麼接下來就是造車架、造輪子、造沙發。

  1. Body(){

  2.   //to do something

  3.   return this;

  4. }

  5. Tyre(){

  6.   //to do something

  7.   return this;

  8. }

  9. Sofa(){

  10.   //to do something

  11.   return this;

  12. }

反過來,抽象依賴於具體實現,舉個例子(把上面的改造一下):造一輛車,先造個輪子,根據輪子再去做個車架,根據車架再去完成沙發。

  1. Car(){

  2.     var tyre = new Tyre();

  3.     this.setTyre(tyre);

  4.     var body = new Body(tyre);

  5.     this.setBody(body);

  6.     var sofa = new Sofa(body);

  7.     this.setSofa(sofa);

  8.     return this;

  9. }

  10. Body(tyre){

  11.   //to do something

  12.   return this;

  13. }

  14. Tyre(){

  15.   //to do something

  16.   return this;

  17. }

  18. Sofa(body){

  19.   //to do something

  20.   return this;

  21. }

這樣的話,如果輪胎變化了,可能就會影響整個後面流程。  總結一下:具體實現依賴抽象,就是提前定義和約定好每個抽象,然後分別去實現抽象,再去具體實現,做到心中有數,各個擊破。  而抽象依賴具體實現,就是先做一個事情,再考慮下一個事情,沒有提前規劃完善,過程中就容易出問題。

接口隔離原則

客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。  這個原則很簡單,盜用兩個圖如下,對於java等語言來說,B和D類依賴“接口I“(圖1),但是B並沒有使用方法4、5,D沒有使用方法2、3,但是因為依賴接口I,所以也需要定義並不使用的方法。  從前端角度來說,一個公共類它可能被不同的其它類取用,但是每個類只需要用到公共類的其中一個方法,但是卻需要把公共類全部引入,這樣就顯得太臃腫。

所以通過把一個大接口拆分成幾個小接口,可以使代碼更精準,取用更靈活。  接口可以儘量小,但是要有限度。  對接口進行細化可以提高程式設計靈活性是不掙的事實,但是如果過小,則會造成接口數量過多,使設計複雜化,所以一定要適度。

迪米特法則

迪米特法則又叫作最少知道原則,就是說一個物件應當對其他物件有盡可能少的瞭解。舉個例子:我們定義一個類,定義一個變數,會使用get,set方法來控制這個變數讀寫代碼如下:

  1. var obj = {

  2.   val:0,

  3.   setVal:function(val){

  4.       this.val = val;

  5.       alert(val);

  6.   },

  7.   getVal:function(val){

  8.       return this.val;

  9.   }

  10. }

  11. function xxx(){

  12.     obj.setVal(666);

  13. }

如果我們需要在修改val值的時候,彈出一個提示框告訴我們最新的值,那麼我們可以把alert寫在setVal方法內,在其他物件中使用obj物件的時候,只需要使用setVal方法,obj內部發生了什麼當前類並不知道,當前類只能用暴露出來的set方法,而不需要知道set方法做了什麼事情。

反過來,我們拋棄這個原則寫一個代碼例子:

  1. var obj = {

  2.   val:0

  3. }

  4. function xxx(){

  5.     obj.val = 666;

  6.     alert(obj.val);

  7. }

這樣xxx方法就非常瞭解obj這個物件了,因為在它內部直接操作了obj物件。真的如此就一點封裝都沒有了,在其他地方也會出現非常多的重覆代碼。  通俗的來講,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供公共方法,不對外泄漏任何信息。

開閉原則

軟體中的物件(類,模塊,函式等等)應該對於擴展是開放的,但是對於修改是封閉的。在軟體開發和迭代過程中,常常可能需要修改邏輯,比如一個公用的方法Func在不同地方被使用,在某一個方法體中需要修改這個公用方法的邏輯來達到當前的需求。

但是修改此公用方法Func必然會影響到其他地方,然而其他地方需要保持原方法的邏輯,所以這裡修改是被禁止的。  那麼可以新增一個方法Func2,替換當前方法的取用,如此就是所謂的擴展,擴展是開放的。

再舉一個常見的例子,對於訂單資料,最開始我們定義了訂單的狀態status:1-2分別代表 未完成和已完成。後來訂單開始付費了,我們需要更多的狀態,已支付和未支付。

那麼支付狀態和訂單完成狀態並不完全獨立,要能同時表示用一個欄位表示這兩種狀態,需要改變status的定義,比如1代表未完成並且未支付,2代表未完成並且已支付,如此修改之後,以前的所有判斷和邏輯會發生變化,舊的資料也無法兼容,系統幾乎很難迭代下去,代價也很大。

那麼如果擴展一個欄位payStatus,就不需要修改資料庫定義,前端也只需要在以前的邏輯上多一個payStatus狀態的判斷。  開閉原則的擴展開放,修改封閉,是很有必要的。

6 個實體詳解如何把 if-else 代碼重構成高質量代碼

Java 中的 try catch 影響性能嗎?

Get史上最優雅的加密方式!沒有之一!

如何 “幹掉” if...else

END

赞(0)

分享創造快樂

© 2021 知識星球   网站地图