[ASP.NET]91之ASP.NET由淺入深 不負責講座 Day22 - DIP 依賴反轉原則
前言
今天要介紹的是Dependency Inversion Principle, DIP,依賴反轉原則。
跟IoC(控制反轉)是一樣的意思,另外相關的就是Dependency Injection,則是實作DIP的一種方式。
用到的觀念有 『多型』、『Liskov Substitution Principle』跟『Open/Close Principle』。
Issues
- 定義
High level modules should not depend upon low level modules.
Both should depend upon abstractions.
Abstractions should not depend upon details.
Details should depend upon abstractions.
抽象等級高的模組,不應該依賴於抽象等級低的模組,
兩者都應該依賴abstraction。
abstract不應該依賴於細節。
細節應該依賴於abstract。
恩,難懂的要死,這些原則從字面上都不好懂,翻成中文就更不好懂了。
- 具體意思
模組間的依賴關係,是透過抽象介面而發生的。實現class之間不發生直接的依賴關係,其依賴關係是通過interface或abstract class產生的。
因為只要實作interface的,都要滿足interface的contract內容,這樣我才可以讓你注進來(dependency injection)。
- 簡單的說
- 也就是class之間的耦合關係,要多墊一層interface/abstract來隔開。
- 高層要用低層的方法,就都透過interface來表述抽象的行為,而不用管實體的concrete class是什麼。(滿足多型與Liskov substitution principle)
- 系統抽象程度看interface, reuse程度看abstract
系統抽象程度,看interface就是因為這樣,抽象代表不管實作,代表系統可以穩定。因為通常改變的都是實作內容,當需要抽換邏輯時,只需要改變注入的concrete class即可。符合開放封閉原則。
- 結論
- 每個class都應該盡量有abstract class或interface,避免與外界互動時,直接耦合
- 變數類型,應該盡量是abstract或interface
- 基本上,任何一個concrete class,都不應該是繼承另一個concrete class的。如果把繼承鏈想像成tree,代表所有leaf才可以是concrete class。
- 避免覆寫abstract class已經決定的concrete function,會對系統造成不穩定性,因為不知道從繼承鏈的哪邊改起。
- 請滿足Liskov SubStitution Principle。
- 例子
剛好這幾天在下大雨,我們就舉個例子好了。
場景:下雨,我們要拿東西遮雨。- 現在的需求是我們拿雨傘來遮雨,所以我們的Code就長這樣:
- 我們把需求改成用陽傘來遮雨
/// <summary> /// 需求變更,我們現在要改成用陽傘來遮雨。 /// </summary> public void RainnyDayWithSunUmbrella() { 天氣 weather = new 天氣(); if (天氣.IsRain) { 陽傘 umbrella = new 陽傘(); umbrella.遮雨(); } }
可以看到,因為現在要用陽傘了,我們必須得『修改』我們的程式碼,把雨傘改成陽傘。
這違反了Open/Close Principle。
因為高層模組(也就是Client),希望使用的遮雨方法,依賴於低層模組。
- 我們來看,什麼是依賴反轉。依賴反轉在這個case的重點,就是讓原本場景依賴於細節class的方法,改成細節class的方法依賴於抽象介面。
/// <summary> /// 當下雨天,我們就new一把雨傘來遮雨 /// </summary> public void RainnyDayWithUmbrella() { 天氣 weather = new 天氣(); if (天氣.IsRain) { 雨傘 umbrella = new 雨傘(); umbrella.遮雨(); } }
在這個Case裡面可以看到Client Class(也就是場景類)的RainnyDayWithUmbrella(),裡面直接與雨傘這個claas耦合,因為RainnyDayWithUmbrella()需要用到雨傘來遮雨()。
這個動作就違反了DIP。
/// <summary> /// 我們care的是,下雨,要遮雨(interface/abstract)。 /// 用什麼(concrete class)遮,則不會影響我們Client Class的抽象邏輯(下雨,遮雨)。 /// </summary> public void RainnyDayWithDIP() { 天氣 weather = new 天氣(); if (天氣.IsRain) { I天氣應變措施 action = GetSomethingCoverBody(CoverStuff.雨傘); action.遮雨(); } } private I天氣應變措施 GetSomethingCoverBody(CoverStuff coverThing) { switch (coverThing) { case CoverStuff.包包: return new 包包(); case CoverStuff.帽子: return new 帽子(); case CoverStuff.雨傘: return new 雨傘(); case CoverStuff.陽傘: return new 陽傘(); case CoverStuff.none: default: return null; } } enum CoverStuff { none=0, 雨傘, 陽傘, 帽子, 包包 }
如同註解所說,我們的高層模組要的是抽象化的行為與邏輯,
我們care的是,下雨,要遮雨(interface/abstract)。
用什麼(concrete class)遮,則不會影響我們Client Class的抽象邏輯(下雨,遮雨)。
[註]GetSomethingCoverBody()這個方法,則可以套用factory pattern,new的部分則可以套用Dependency injection的framework來改寫,可以讓耦合的程度降到最低。 - 現在的需求是我們拿雨傘來遮雨,所以我們的Code就長這樣:
blog 與課程更新內容,請前往新站位置:http://tdd.best/