依賴反轉原則 (DIP)

★★★★★Studied

FromAgile Principles, Patterns, and PracticesFunctional Design

📚 From the Books

依賴反轉原則 (DIP)ConceptPrinciples

A. 高階模組不該依賴低階模組,兩者都該依賴抽象;B. 抽象不該依賴細節,細節該依賴抽象。這是 OO 設計與程序式設計最根本的分界——被稱為「良好 OO 設計的標誌」,也是分層架構與框架的基石。它跟 DI 不同:DIP 是原則,DI 是達成它的工法之一。

🧠 When to Use

TIP

訊號:核心的商業策略碼裡直接 new 出某個資料庫 / 硬體 / 第三方類別;想把高階邏輯搬到另一個情境重用,卻被低階細節綁死拖不動。

⚖️ Structure & Variants

所有權反轉 — Button / Lamp
// ❌ 天真設計:Button 直接依賴具體 Lamp,換不了裝置
class Button(private val lamp: Lamp) {
    fun poll() { if (pressed()) lamp.turnOn() }
}

// ✅ 引入由『高階模組』定義的抽象,Lamp 去實作它
interface SwitchableDevice { fun turnOn(); fun turnOff() }

class Button(private val device: SwitchableDevice) {   // 依賴抽象
    fun poll() { if (pressed()) device.turnOn() }
}
class Lamp : SwitchableDevice { /* ... */ }             // 細節依賴抽象
  • 關鍵:介面 SwitchableDevice使用它的 Button 那一層擁有,而非 Lamp 那一層——這是「所有權反轉(ownership inversion)」,也就是 Hollywood 原則「Don’t call us; we’ll call you」。
  • 命名要反映高階語境:叫 SwitchableDevice 比叫 ILamp 好,因為 Button 不只為了控制燈泡而存在。
把演算法從硬體上剝下來 — Regulate
// ❌ 調節策略(高階)綁死在硬體 I/O 位址(低階)
fun regulate(minTemp: Double, maxTemp: Double) {
    while (true) {
        while (input(THERMOSTAT) > minTemp) wait(1); output(FURNACE, ENGAGE)
        while (input(THERMOSTAT) < maxTemp) wait(1); output(FURNACE, DISENGAGE)
    }
}

// ✅ 依賴抽象,同一套演算法能配任何溫度計 + 加熱器
interface Thermometer { fun read(): Double }
interface Heater { fun engage(); fun disengage() }

fun regulate(t: Thermometer, h: Heater, minTemp: Double, maxTemp: Double) {
    while (true) {
        while (t.read() > minTemp) wait(1); h.engage()
        while (t.read() < maxTemp) wait(1); h.disengage()
    }
}
  • 高階策略完全與硬體無關,可重用於暖爐、熱水器……任何實作這兩個介面的組合。
經驗法則(與其例外)
  • 不讓變數持有具體類別的參考;不繼承具體類別;不覆寫父類已實作的方法。
  • 但這是針對易變的具體類別。像 String 這種幾乎不會變的具體類別,依賴它並無大礙——別把法則當教條。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading

🔗 Dive Deeper