Dependency inversion principle (DIP) 依賴反轉原則

原始碼的依賴關係應只涉及抽象而不涉及具體

Huashen
Apr 17, 2021

什麼是DIP

依賴反轉原則,簡稱DIP(Dependency inversion principle),告訴我們該如何讓系統依賴性降至最低。DIP的說明:原始碼的依賴關係應只涉及抽象而不涉及具體。這裡的抽象通常指的是介面抽象類別(Abstract Class)。我們在上一篇ISP介面隔離原則中有簡單介紹過介面,以下說明抽象類別

抽象類別跟介面的語法很相似,但應用的場景有所不同。介面主要用於定義功能,而抽象類別用於定義子類別大致的輪廓,再將部分細節交由子類別實作

上面定義了三種介面,以及一個抽象類別。抽象類別就如同一般類別一樣,可以實作多個介面,不同的地方在於抽象類別可以選擇不實作某些方法,交給子類別去實作。

子類別必須實作出父類別的抽象方法,或是繼續維持抽象給下一層子類別實作,而未抽象的方法就可以共用,不須重新定義。

介面像是規格計畫書,實作的類別都必須有介面定義的功能。而抽象類別像是按計劃書設計的半成品,將大部分功能組裝起來,留下不同的部分給子類別實作,也因為是半成品,所以不可以實例化抽象類別。等最終的子類別將抽象類別的抽象方法實作之後,該子類別才能被實例化。

為什麼要有DIP

物件導向的設計方式主要是將屬性及方法利用類別封裝,再實例化產生物件,利用物件之間的互動來建構系統,因此依賴性(耦合性)必不可少。但耦合性高的程式一直是我們不樂見的,它會造成我們系統未來的擴充、修改、維護的困難。我們介紹過的SOLID原則,及未來的設計模式,都是在告訴我們如何降低系統耦合度、提升可讀性可擴充性

沒有妥善設計的程式碼,物件之間連結緊密、耦合度高、擴充性低,原因是物件彼此之間直接的的依賴,造成程式碼改動困難,若我們利用DIP依賴反轉原則,讓彼此不直接相依,而是透過介面或抽象類別來互動,就能夠大大改善系統的耦合度。

如何做到DIP

剛才提到過,物件導向設計是利用物件之間彼此的互動來構成整體系統。通常用戶操作的都是大物件,而大物件再跟許多相關的小物件互動,使小物件去做我們要做的事情。

客戶想要一台車,於是我們創建了一台簡單的車子,用戶在啟動車子的時候,實際上是車子呼叫引擎的方法,來達成車子的啟動。

客戶試過這台車之後,覺得馬力不夠大,想要換成柴油引擎,現在為了這個新需求,我們需要修改車子的程式碼

很明顯的,這違反了我們之前提過的OCP開放封閉原則,因為只要我們更換引擎,就需要重新修改程式碼,這樣對系統是個隱患。會造成如此問題是因為:車子直接相依於引擎的具體實作

Car類別直接相依於DieselEngine及PetrolEngine,違反DIP。

DIP告訴我們,物件不應該相依於具體,而應該相依於抽象。因此我們最好在車子設計之初,就做好可以隨時面對變化的架構。前面應用了許多介面,因此這次我們利用抽象類別來設計新的引擎。

我們利用一個抽象的引擎類別來當中間層,讓引擎跟車子彼此不互相依賴,彼此都依賴於這個抽象引擎

Car類別跟DieselEngine、PetrolEngine都相依於Engine這個抽象類別。

我們已經把依賴關係轉移到抽象層上了,但這還不夠,因為我們若是要更換引擎,還是得修改Car裡面引擎的實例,因此仍然不符合OCP。

依賴注入Dependency Injection

要讓我們的程式滿足OCP其實很簡單,我們只要將引擎的實例化跟Car類別分離就好,利用依賴注入Dependency Injection(簡稱DI)就可以解決這個問題,在這裡我們先跳過解釋,直接看程式碼。

我們在Car類別的constructor裡指定傳入Engine類別的物件,這樣就可以達到更換引擎而不必修改程式碼的目標了,這種把依賴關係注入到物件中的方式就叫做依賴注入

但是這種從constructor注入依賴的方式有其限制,一來是我要更換引擎就勢必要重新創建Car的物件,二來是當系統複雜時,注入的依賴變多,管理起來會更加麻煩,因此常見的依賴注入會用另一種方法達成。

這次使用Car類別提供的setEngine方法來注入,我們就可以隨時變換使用的引擎,這種作法需要在start的時候檢查engine是否已被注入,若沒有則要另外處理,這麼做也有麻煩的地方,因此最好的方法就是同時使用兩者。在創建Car的時候透過constructor注入預設的engine,面臨需要隨時抽換的狀況就用setEngine來替換,這樣就可以避免沒有注入的情形了。

總結一下

所有物件的依賴關係都應該依賴於抽象,我們可以用介面或是抽象類別來做到,更可以利用依賴注入的方式進一步降低耦合度。

這篇文章關於DIP的說明反而較少,原因是大部分都聚焦在抽象類別跟依賴注入,因為大部分的觀念其實在前面已經或多或少有提過了,因此這裡花多一些篇幅講一些前面沒提過的東西。SOLID原則彼此都是互相有關聯的,相互結合之下就容易寫出易擴充、穩定度高的程式碼,在設計模式的路上也會持續看到他們的身影。

下一篇文章 <單例模式(Singleton pattern)>

如果對於我的文章或程式碼有任何問題,歡迎在下方留言指教。

若有幫助到你,也歡迎給文章拍手一下,讓我在寫文章的路上更加進步!

--

--

Huashen

嗨,我是Huashen,一位軟體工程師,這裡會記錄我的程式設計心得與筆記。