兩個類別互相依賴(循環依賴)怎麼解開?

Medium★★★★Studied

FromRefactoring for Software Design SmellsAgile Principles, Patterns, and Practices

Concepts practiced📏 依賴反轉原則 (DIP)📏 迪米特法則 (Law of Demeter)

Convention: read the requirements & constraints first, sketch your own design, then expand the reference to compare.

📚 From the Books

兩個類別互相依賴(循環依賴)怎麼解開?ProblemMedium反模式辨識

A 依賴 BB 又反過來依賴 A(或繞一圈間接回來)。兩者變成必須一起理解、一起改、一起測、無法各自重用的「連體嬰」。這是循環依賴——怎麼解開這個環?

🎯 Scenario

// SecureDocument 把自己整個傳給 DESEncryption
class SecureDocument {
    fun encrypt(enc: DESEncryption): SecureDocument = enc.encrypt(this)  // 傳 this
}
// DESEncryption 反過來認得 SecureDocument 的一切 —— 雙向相依
class DESEncryption {
    fun encrypt(doc: SecureDocument): SecureDocument { /* 讀 doc 的內部 */ }
}

壞味道:違反無環相依原則(Acyclic Dependencies Principle)。改 A 影響 BB 又繞回影響 A——連鎖效應從同一處出發又回到原點。書中真實案例是 java.util 六個抽象糾成一團「亂麻(tangle)」,大型間接環在複雜系統中幾乎肉眼看不見,是隱蔽 bug 的溫床。

🧠 Intuition

TIP

先別看下面。問自己:這個環裡,誰是「高階策略」、誰是「低階細節」?如果讓它們都去依賴一個抽象,而不是彼此的具體類別,箭頭會怎麼改向?(提示:把其中一條依賴反轉。)

⚖️ Reference Design

常見成因 — 對號入座
  • 責任放錯位置:成員擺到不該擺的類別,彼此互引成環。
  • 隨手傳 this:方法呼叫時把自己整個物件交出去,造成雙向暴露(正是上面的例子,也踩到迪米特法則)。
  • 回呼實作不當:對稱依賴沒用 Observer 之類的樣式解開。
解法一:用 DIP 反轉一條依賴
// 讓『被依賴的一方』依賴由『使用方』定義的抽象,箭頭同向、環被打斷
interface Encryptable { fun bytes(): ByteArray }   // 由 DESEncryption 這一側需要而定義

class DESEncryption {
    fun encrypt(target: Encryptable): ByteArray { /* 只認得 Encryptable,不認得 SecureDocument */ }
}
class SecureDocument : Encryptable {                 // 細節依賴抽象
    override fun bytes(): ByteArray = TODO()
    fun encrypt(enc: DESEncryption) = enc.encrypt(this)
}
  • DIP:抽出介面、把其中一條「具體 → 具體」的邊改成「具體 → 抽象」,環就被打斷。介面由需要它的那一側擁有(所有權反轉)。
解法二:只傳需要的東西,別傳整個 this
  • 別把 this 整包交出去——只傳對方真正需要的資料或一個窄介面(迪米特法則:只跟直接朋友說話、不暴露內部)。
  • 對稱的通知關係改用 Observer:subject 不認得具體 observer,只認得 observer 介面,解開雙向耦合。
  • 若兩者責任其實該合併或該搬家,先把責任放對位置,環往往自然消失。

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading