SOLID 依賴反轉原則

SOLID 依賴反轉原則(DIP, Dependendy Inversion Principle)

-5 min read

高層模組不應該依賴於低層模組,而是應該依賴於抽象。換句話說,模組之間的依賴應該是透過抽象介面實現的,而不是直接依賴於具體實現。這樣可以降低模組之間的耦合度,提高系統的靈活性和可維護性。

依賴介面,而不是依賴實作。

「依賴反轉原則」和「介面隔離原則」有一些相似之處。兩者都關注於將軟體中的高階模組與低階模組進行分離,並且避免將高階模組依賴於低階模組。介面隔離原則通常關注於如何設計介面,以便讓不同的使用者(如不同的模組或客戶端程式)能夠根據自己的需要使用介面的不同部分。依賴反轉原則則更注重於如何管理模組之間的依賴關係,以便實現可重用、可擴展和可測試的程式設計。

生活中的範例

小明跟小美是情侶,但小明都會直接稱呼對方名稱,這時候只要有小三或換對象,就有講錯的可能。

當小明與小花偷偷約會的時候,不小心叫成小美,因為這樣的叫法,較需要花費力氣去記憶,且較容易出錯。

小明應該依賴女朋友抽象類別,之後統一叫女朋友。 在跟不同女友約會的時候,因為有統一依賴抽象,都叫對方女朋友,就再也不會有這個問題了。

更接近程式設計的範例

小明喜歡玩遊戲,他想要在家裡玩電腦遊戲,但他的電腦畫面不流暢,需要升級顯示卡。他決定到電腦店去買一張新的顯示卡。

但是小明不知道自己應該選擇哪種顯示卡,於是他詢問電腦店的店員。店員告訴他可以選擇 NVIDIA 或 AMD 顯示卡,然而店員並沒有告訴小明這兩種顯示卡是怎麼運作的,也沒有告訴小明怎麼安裝。

這時候,小明可以選擇去參考顯示卡廠商的網站,看看顯示卡的規格,或是上網搜尋如何安裝顯示卡。這些資源都是獨立的,不需要依賴店員的建議或指導。

在這個例子中,小明依賴了顯示卡廠商的網站和網路上的資源,而不是依賴店員的建議或指導。這就符合依賴反轉原則,讓高層模組依賴於低層模組的抽象,而不是低層模組依賴於高層模組的細節。

在顯示卡這個例子中,小明不符合依賴反轉的狀況是在購買顯示卡時只聽店員的話,而沒有參考其他來源或評估他自己的需求。如果小明依賴於店員的建議而沒有考慮自己的需求和其他可行的選擇,那麼這就是不符合依賴反轉原則的情況。因此,在現實生活中,當我們需要做出重要決定時,需要獨立思考、評估選擇,而不是單純依賴他人的建議。

JavaScript 範例

錯誤範例

範例中,UserController 直接實例化了 UserService 並使用其提供的方法。這樣的做法使得 UserControllerUserService 相互依賴,且 UserController 只能使用 UserService 中提供的方法。這樣的耦合度很高,當系統變得更加複雜時,這種耦合度會使系統難以擴展和維護。

class UserService {  getUserById(id) {    // ...  }}class UserController {  constructor() {    this.userService = new UserService()  }  getUserById(id) {    const user = this.userService.getUserById(id)    // ...  }}

正確範例

UserController 的建構函數接受一個 userService 參數,這樣可以讓 UserController 依賴於抽象,而不是具體的 UserService。當系統需要擴展或者重構時,只需要替換掉 userService 對象即可,而不需要修改 UserController 的代碼。

class UserService {  getUserById(id) {    // ...  }}class UserController {  constructor(userService) {    this.userService = userService  }  getUserById(id) {    const user = this.userService.getUserById(id)    // ...  }}const userService = new UserService()const userController = new UserController(userService)

參考文章