設計模式六大原則(翻譯)
六大原則:
原文連結:https://medium.com/@mena.meseha/6-principles-of-software-design-3a8478954e1c
1 單一職責原則:
定義:
單一職責原則,也被稱為單一功能原則。就是說,使類變化的原因不超過一個。通俗地講,一個類只負責一個功能。
原則:
如果一個類有太多的功能,也就是說有很多功能都聚合到一起,一個功能的改變可能弱化類的其他功能。
這種耦合會導致脆弱的設計,當變化發生時,設計會遭受意外的破壞。如果想避免這種情況的發生,
應該儘可能地遵循單一職責的原則。這個原則的核心是解耦和加強內聚。
問題的起源:
類T負責兩個不同的職責:職責P1,職責P2。當類T由於職責P1需要修改時,可能會導致原本正常執行的P2功能出現故障。
也就是說,職責P1和P2是耦合在一起的。
原因:
程式設計師都知道應該編寫出高內聚低耦合的程式,但是很多耦合經常在不經意間發生,因為職責分散。
解決方案:
一個類或者模組只負責一個功能。
優點:
- 可以降低類的複雜性。一個類只負責一個功能,其邏輯肯定比負責多個功能簡單得多;
- 提高類的可讀性,提高系統的可維護性;
- 變更帶來的風險降低了,變更是不可避免的。
- 如果很好地遵循單一職責原則,在修改一個功能時,可以顯著減少對其他功能的影響。
2 里氏替換原則(LSP):
定義:
面向物件設計的基本原則之一,任何基類出現的地方都可以被子類替換。
LSP 是繼承和重用的基礎。只有派生類可以替代基類,不影響軟體單元的功能,才能真正複用基類,
派生類也可以在基類的基礎上增加新的行為。即如果父類是功能模組的一部分,則使用子類代替父類,功能模組可以正常執行,子類例項也可以代替父類例項
繼承的意思是,父類中已經實現的所有方法(相對於抽象方法),實際上都是在設定一系列的規範和約定,
雖然沒有強制要求所有的子類都必須遵循這些契約,但是如果子類隨意修改這些非抽象的方法,會對整個繼承系統造成破壞。
這就是里氏替換原則。
繼承作為面向物件的三大特性之一,給程式設計帶來了極大的便利,但也帶來了弊端。
例如,使用繼承會給程式帶來入侵,降低程式的可移植性,增加物件之間的耦合。
如果一個類被其他類繼承,則需要修改該類時必須考慮所有子類、類,以及修改父類後,所有涉及子類的函式都可能會失敗。
實際情況:
在實際程式設計中,我們經常通過重寫父類的方法來完成新的功能,這樣雖然寫起來簡單,但是整個繼承系統的複用性會很差,
尤其是多型使用比較頻繁的時候。執行錯誤的機率非常高。 如果要覆蓋父類的方法,
比較常見的做法是: 原來的父類和子類繼承一個比較通用的基類,取消原來的繼承關係,改為使用依賴、聚合、組合等關係。
總結:
子類可以擴充套件父類的功能,但不能擴充套件父類的原有功能。
- 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。
- 子類可以新增自己獨特的方法。
- 當子類的方法覆蓋父類的方法時,該方法的前提條件(即方法的形參)比父類的輸入引數更寬鬆。
- 當子類的方法實現了父類的抽象方法時,方法的後置條件(即方法的返回值)比父類更嚴格。
3 依賴倒轉原則:
定義:
上層模組不應該依賴下層模組,上層和下層模組都應該依賴它們的抽象;抽象不應該依賴於細節;細節應該依賴於抽象。
區別:
對於面向過程的開發,上層呼叫下層,上層依賴下層。當下層發生劇烈變化時,上層也需要發生變化,導致模組的複用性降低,大大增加了開發成本。
面向物件的開發很好地解決了這個問題。一般來說,抽象變化的概率很小,使用者程式依賴於抽象,實現細節依賴於抽象。即使實現細節不斷變化,
只要抽象不改變,客戶端程式也不需要改變。這大大降低了客戶端程式和實現細節之間的耦合。
問題的根源:
A類直接依賴B類,如果要將A類改成依賴C類,必須通過修改A類的程式碼來實現。 這種場景下,A類一般是負責複雜業務邏輯的高層模組;
B 類和 C 類是負責基本原子操作的低階模組;如果修改A類,會給程式帶來不必要的風險。
解決方案:
修改A類依賴介面I。B類和C類各自實現介面I。A類通過介面I間接與B類或C類通訊,大大減少了修改A類的機會。
依賴倒置的原理是基於這樣一個事實,即抽象事物(介面或抽象類)比細節的可變性要穩定得多。建立在抽象上的架構比建立在細節(特定的實現類)上的架構要穩定得多。
傳遞依賴的方式有介面傳遞、建構函式傳遞、設定方法傳遞三種方式
編碼要求:
- 低階模組應該有抽象類或介面,或者兩者都有。
- 變數的宣告型別儘可能是抽象類或介面。
- 使用繼承時遵循里氏置換原則。
4 介面隔離原則ISP
定義:
客戶端不應依賴它不需要的介面;一個類對另一個類的依賴性應基於最小的介面。
問題的根源:
A 類通過介面 I 依賴於類 B,C 類通過介面 I 依賴於 D 類。如果介面 I 不是類 A 和類 B 的最小介面,則類 B 和類 D 必須實現它們不需要的方法。
解決方案:
將臃腫的介面 I 拆分為單獨的介面,A 類和 C 類分別與它們所需的介面建立依賴關係,介面隔離原理。
因此:
建立單一的介面,不要構建大而臃腫的介面,儘量細化介面,介面中的方法儘可能小。也就是說,我們需要為每個類建立一個專用介面,而不是嘗試為依賴於它的所有類構建一個非常大的介面。
注意:
- 介面功能儘可能少,細化介面可以提高程式設計的靈活性,但如果介面太小,它會導致太多的介面並使設計複雜化,所以一定要適度。
- 為依賴於介面的類自定義服務僅向呼叫類公開它需要的方法,並且它不需要的方法被隱藏。只有專注於為模組提供自定義服務,才能建立最小的依賴關係。
- 提高凝聚力並減少外部互動,使介面用最少的方法做最多的事情。
5. 最小知道原則
定義:
這意味著一個物體應該對其他物體有儘可能少的知識,並且不與陌生人交談,英語縮寫為:LOD。
通俗地說,一個類對它所依賴的類瞭解得越少越好。也就是說,對於依賴類,無論邏輯多麼複雜,邏輯都儘可能地封裝在類中,外部提供的公共方法不會洩露任何資訊。另一種說法:只與直接的朋友溝通
直接朋友:
成員變數中的類、方法引數、方法返回值、
間接朋友:
區域性變數中的類
問題的根源:
類與類之間的關係越密切,耦合程度越高。當一個類更改時,對另一個類的影響更大。
解決方法:
嘗試減少類和類之間的耦合。
注意:
迪米特定律的初衷是減少類之間的耦合。由於每個類都減少了不必要的依賴關係,
因此它確實可以減少耦合關係。但凡事都有一定程度的,雖然可以避免與間接類的交流,但要進行交流,勢必要通過"中介"來連線。
過度使用Dimit原則將導致大量此類中介和交付類,從而導致系統複雜性增加。
因此,在使用Dimitte規則時,我們必須反覆權衡天平,使結構清晰,同時又具有高內聚性和低耦合性。
6. 開-閉原理
定義:
軟體實體(如類、模組和函式)應開放給擴充套件,不開放給修改。
問題的根源:
在軟體生命週期中,當軟體的原始程式碼由於更改、升級和維護而需要修改時,可能會在舊程式碼中引入錯誤,或者可能導致我們重構整個功能。存在已重新測試的程式碼。
解決方法:
當需要更改時,請嘗試通過擴充套件來實現更改,而不是修改現有程式碼來實現更改。
總結一下:
使用抽象生成框架通過實現擴充套件詳細資訊。 因為抽象的靈活性好,適應性廣,只要抽象合理,軟體架構基本可以保持穩定。
而軟體中的變數細節,我們用抽象派生的實現類來擴充套件,當軟體需要改變的時候,我們只需要重新派生一個實現類來根據需求進行擴充套件。
當然,前提是我們的抽象應該是合理的,我們必須前瞻性地預測需求的變化。