依賴反轉原則入門:從手作甜點店學習dip

依賴反轉原則入門:從手作甜點店學習dip

依賴反轉原理的核心概念

什麼是依賴反轉?

💡
本文涵蓋多個技術細節,但關鍵重點是「依賴反轉就是依賴抽象」。

「業務邏輯」和「實作細節」兩端都依賴抽象
業務邏輯 ➡️ 抽象介面 ⬅️ 實作細節

用手作甜點店比喻依賴關係

舉例來說:你經營一家手作甜點店 (業務邏輯/Domain層):

  • 需要食材來製作甜點

  • 但你不親自去買

  • 告訴採購部需求(Repository介面)

採購部會:

  • 選擇供應商可能是批發市場、農場直送、進口等

  • 買到食材(實作Repository)

這樣設計的好處

  • 你可以專注在做甜點,你不用管食材怎麼買 →業務邏輯 依賴抽象 ⬇️

  • 告訴採購部需求→Repository介面

  • 採購部選擇供應商買食材→實作抽象Repository 依賴 ⬆️

原則

維基百科:https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99

  • 高層次的模組不應該依賴於低層次模組

    • 高層次模組:抽象、業務規則、接近人思維

    • 低層次模組: 實作細節、數據操作、接近電腦運作

    • 依賴:處理怎麼做

    • 目的:解耦高低層模組,低層細節變更時,不影響高層業務邏輯

  • 抽象接口(介面)不應該依賴於具體實現,而具體實現應該依賴於抽象接口(介面)

    • 意思是抽象接口不要有具體的實作細節
    // ❌ 錯誤:介面包含市場A的具體實作細節
    interface Purchase {
        // 依賴特定市場的營業時間
        fun buyBeforeMarketAClose()

        // 依賴特定市場的付款方式
        fun payWithMarketACreditCard()

        // 依賴特定市場的食材
        fun getMarketAIngredients(): MarketA
    }

    // ✅ 正確:介面只定義要做什麼,不管是從哪裡買
    interface Purchase {
        // 純抽象的採購行為
        fun buy()

        // 使用通用的食材型別
        fun getIngredients(): List<Ingredient>
    }
  • 抽象:專注在做什麼而非怎麼做,目的是隱藏實作細節。

    • 實現方式:介面 (Interface)、抽象類別 (Abstract Class)
  • 目的:透過接口,允許不同的具體實作輕鬆替換。

依賴說明

  • 依賴=直接處理怎麼做

    • 實作程式細節

    • 你親自去採購

  • 依賴反轉=關注要做什麼

    • 透過介面告知需求

    • 只要告訴採購部需求

為什麼要依賴反轉?

有無使用依賴反轉的差異

比喻甜點師 ➡️ 超市A(實作程式)甜點師 ➡️ 採購部(介面) ⬅️ 超市A/批發商B/農場C
依賴狀態沒有依賴反轉有依賴反轉
特點綁定特定實作、更換供應商需改動業務邏輯透過介面解耦、實作可自由替換

程式碼示範:

// 沒有依賴反轉:
class ChefService {
    // 依賴特定超市
    private val marketA = MarketA() 

    fun getIngredients() {
        marketA.buy()  // 綁定實作
    }
}

// ------------------------------------

// 有依賴反轉:
interface Purchase {  // 採購部介面
    fun buy()
}

class ChefService { 
    // 依賴介面
    private val purchase: Purchase

    fun getIngredients() {
        purchase.buy()  // 透過介面呼叫
    }
}

// 不同供應商實作介面
class MarketA : Purchase {
    override fun buy() { /* 實作 */ }
}

class MarketB : Purchase {
    override fun buy() { /* 實作 */ }
}

class Farm : Purchase {
    override fun buy() { /* 實作 */ }
}

雖然我們實現了依賴反轉,但這樣的程式碼還是無法執行,因為 purchase 沒有被初始化。需要透過依賴注入(DI)來實現。

class ChefService(
    private val purchase: Purchase  // 透過建構子注入
) {
    fun getIngredients() {
        purchase.buy()
    }
}

總結

💡 依賴反轉概念:

  • 業務邏輯不應依賴實作細節

  • 兩者應該依賴抽象 (介面)

✨ 優點:

  • 業務邏輯穩定性高

    • 實作方式改變時,核心邏輯不需修改
  • 程式靈活

    • 可替換不同實作(如:更換供應商)
  • 方便測試

    • 可替換測試用的模擬實作

🔑 判斷重點:

  • 實作 = 依賴

  • 介面 = 依賴反轉

💡
本文涵蓋多個技術細節,但關鍵重點是「依賴反轉就是依賴抽象」。

下一章依賴注入(之後更新)