天天看點

子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)

單一職責原則

裡氏替換原則

依賴倒置原則

接口隔離原則

迪米特法則

開閉原則

設計模式不容易用文字描述清楚,而過多的代碼,看起來也讓人摸不到頭腦,加上詞語或者文字描述的抽象感,很容易讓人看了無數設計模式的文章,也仍然了解不了。  是以我一直打算寫此系列部落格,首先我會從大量文章裡去了解這些設計模式,最後我用自己的語言組織轉化為部落格,希望用更少的代碼,更容易了解的文字,來聊一聊這些設計模式。  我所了解、所描述的每一個設計模式也可能有些是錯誤的,甚至也不一定有非常深刻的了解,是以希望有人指出,我可以更改部落格内容。  因為我是前端,是以設計模式的代碼以前端代碼和視角為主。  此部落格内容對每一種模式并不會寫得非常深入,也許能為讀者打通一些認知,如果看了此系列部落格,再去看其他更深入的部落格,可能是一種比較好的方式。

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

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

子類可以實作父類的抽象方法,但不能覆寫父類的非抽象方法。

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

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

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

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

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

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

③ 當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬松。

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

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

需要注意兩點,1對類的繼承關系的定義要搞清楚,2設計要依賴于具體行為和環境。總之子類可以随便擴充,但是别改父類。

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

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

高層次的子產品不應該依賴于低層次的子產品,他們都應該依賴于抽象。

抽象不應該依賴于具體實作,具體實作應該依賴于抽象。

① 高層次的子產品不應該依賴于低層次的子產品,他們都應該依賴于抽象。

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

② 抽象不應該依賴于具體實作,具體實作應該依賴于抽象。

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

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

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

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

用戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。  這個原則很簡單,盜用兩個圖如下,對于java等語言來說,B和D類依賴“接口I“(圖1),但是B并沒有使用方法4、5,D沒有使用方法2、3,但是因為依賴接口I,是以也需要定義并不使用的方法。  從前端角度來說,一個公共類它可能被不同的其它類引用,但是每個類隻需要用到公共類的其中一個方法,但是卻需要把公共類全部引入,這樣就顯得太臃腫。  是以通過把一個大接口拆分成幾個小接口,可以使代碼更精準,引用更靈活。  接口可以盡量小,但是要有限度。  對接口進行細化可以提高程式設計靈活性是不掙的事實,但是如果過小,則會造成接口數量過多,使設計複雜化,是以一定要适度。

子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)
子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)

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

如果我們需要在修改val值的時候,彈出一個提示框告訴我們最新的值,那麼我們可以把alert寫在setVal方法内,在其他對象中使用obj對象的時候,隻需要使用setVal方法,obj内部發生了什麼目前類并不知道,目前類隻能用暴露出來的set方法,而不需要知道set方法做了什麼事情。  反過來,我們抛棄這個原則寫一個代碼例子:

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

軟體中的對象(類,子產品,函數等等)應該對于擴充是開放的,但是對于修改是封閉的。  在軟體開發和疊代過程中,常常可能需要修改邏輯,比如一個公用的方法Func在不同地方被使用,在某一個方法體中需要修改這個公用方法的邏輯來達到目前的需求,但是修改此公用方法Func必然會影響到其他地方,然而其他地方需要保持原方法的邏輯,是以這裡修改是被禁止的。  那麼可以新增一個方法Func2,替換目前方法的引用,如此就是所謂的擴充,擴充是開放的。

再舉一個常見的例子,對于訂單資料,最開始我們定義了訂單的狀态status: 1-2分别代表 未完成和已完成。後來訂單開始付費了,我們需要更多的狀态,已支付和未支付。  那麼支付狀态和訂單完成狀态并不完全獨立,要能同時表示用一個字段表示這兩種狀态,需要改變status的定義,比如1代表未完成并且未支付,2代表未完成并且已支付,如此修改之後,以前的所有判斷和邏輯會發生變化,舊的資料也無法相容,系統幾乎很難疊代下去,代價也很大。  那麼如果擴充一個字段payStatus,就不需要修改資料庫定義,前端也隻需要在以前的邏輯上多一個payStatus狀态的判斷。  開閉原則的擴充開放,修改封閉,是很有必要的。

子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)

有沒有人打賞?沒有的話,那我晚點再來問問。

子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)

關注大詩人公衆号,第一時間擷取最新文章。

子慕談設計模式系列(二)——設計模式六大原則 子慕談設計模式系列(一)子慕談設計模式系列(二)——設計模式六大原則子慕談設計模式系列(三)

如果你有購買鋼琴的打算,可以從這裡了解到在售資訊,價格實惠品質保障。

---轉發請标明,并添加原文連結---

繼續閱讀