當兩個或多個抽象直接或間接相互依賴時,就出現 Cyclically-Dependent Modularization。
核心定義#
這個壞味道違反 Acyclic Dependencies Principle ↗(ADP)與 Ordering Principle。
帶來的代價:
- 處於相依環中的抽象必須一起被理解、變更、使用、測試、重用。
- 改 A 影響 B,B 又會反過來影響 A——連鎖效應從同一處出發又繞回來。
- 大型、間接的環在複雜系統中極難察覺,是隱蔽 bug 的溫床。
名詞釐清#
- 相依圖(dependency diagram):以有向邊表達抽象之間的相依關係。
- 環(cycle):從一個抽象出發、沿邊行走、回到同一抽象的路徑。
- 糾纏(tangle):多個環交織,產生視覺與語意上的「亂麻」。
真實案例:書中展示
java.util套件中六個抽象之間糾結的環。

Figure 5.10: java.util 套件中六個抽象之間的循環
與 Cyclic Hierarchy 的差別#
繼承本身就是子型別對父型別的相依;若父型別也反過來依賴子型別,會產生特殊形式的循環,稱為 Cyclic Hierarchy(6.10 節)。本壞味道指的是非繼承關係的循環。
常見成因#
- 責任放錯位置:成員擺到不該擺的類別後,彼此相互引用形成環。
- 隨手傳
this:方法呼叫時直接把自己整個物件傳給對方,雙向暴露。 - 回呼(callback)實作不當:未使用 Observer ↗ 等樣式來解開對稱依賴。
- 間接相依難以視覺化:在大型系統中肉眼追蹤不到環。
範例#
範例 1:SecureDocument ↔ DESEncryption#
SecureDocument encryptedDocument = desEncryption.encrypt(this);
// DESEncryption 中
SecureDocument encrypt(SecureDocument docToEncrypt);SecureDocument 把 this 傳給 DESEncryption;DESEncryption 反過來知道 SecureDocument 的所有東西——雙向相依。

Figure 5.11: SecureDocument 與 DESEncryption 之間的循環相依
範例 2:Image ↔ Encryption#
醫療影像加密:SecurityManager 透過 Image 取得加密後內容,但 Image.encrypt() 內部又呼叫 Encryption,並把 this 傳過去——Image 與 Encryption 互依。

Figure 5.12: Image 與 Encryption 之間的循環相依
範例 3:Order ↔ OrderImpl#
Order.getAmount() 呼叫 OrderImpl.computeAmount();computeAmount() 又從 Order 取資料——彼此緊耦合。

Figure 5.13: Order 與 OrderImpl 之間的循環相依
範例 4:Order ↔ TaxCalculator#
Order.getAmount() 呼叫 TaxCalculator.computeAmount();後者讀 Order 的全部 items 並結合 calculateTax()——同樣雙向。

Figure 5.14: Order 與 TaxCalculator 之間的循環相依
建議重構#
核心策略:打破環。書中提供四種選項:
- Option 1:為環中的某個抽象引入介面(並讓另一方依賴介面而非具體類別)。
- Option 2:若某個方向的相依其實多餘,直接刪除。可借助 move method/move field 把製造相依的程式碼搬到正確類別。
- Option 3:把製造相依的程式碼搬到第三個抽象裡。
- Option 4:若兩抽象其實在語意上是同一物件,合併為一。

Figure 5.15: 類別 A 與 B 之間的循環相依

Figure 5.16: 透過引入介面打破循環相依(Option 1)

Figure 5.17: 透過移除相依打破循環(Option 2)

Figure 5.18: 透過引入另一個抽象打破循環(Option 3)

Figure 5.19: 透過合併抽象打破循環(Option 4)
flowchart LR
subgraph Original
A1[A] <-->|cyclic| B1[B]
end
subgraph "Option 1"
A2[A] --> I[Interface] <-- B2[B]
end
subgraph "Option 2"
A3[A] --> B3[B]
end
subgraph "Option 3"
A4[A] --> M[Mediator] <-- B4[B]
end
subgraph "Option 4"
AB[A∪B]
end對應到範例#
- 範例 1(Option 1):抽出
IEncryption介面,SecureDocument依賴介面而非DESEncryption。新加AESEncryption不再影響SecureDocument。

Figure 5.20: SecureDocument 與 DESEncryption 的建議重構(範例 1)
- 範例 2(Option 2/3):把「對 Image 加密」這個責任移到
SecurityManager,由它直接呼叫Encryption.encrypt(image),Image不再依賴Encryption。

Figure 5.21: Image 與 Encryption 的建議重構(範例 2)
- 範例 3(Option 4):
OrderImpl只有一個方法且語意上就是 Order,合併之。

Figure 5.22: Order 與 OrderImpl 的建議重構(範例 3)
- 範例 4(Option 2):把
computeAmount()move 到Order,TaxCalculator不再依賴Order。

Figure 5.23: Order 與 TaxCalculator 的建議重構(範例 4)
軼事:缺乏架構師的新創公司#
一家新創公司原由資深架構師設計分層架構,但他離職後,由新人團隊在交期壓力下繼續開發。Business logic 跑進 Buttons/Panels;GraphUtilities 直接引用 GUI 類別。後來相依分析工具顯示跨層的相依環不計其數——任何改動都可能弄壞其他功能。
教訓:
- 必須有專責架構師持續守護整體結構。
- 技術債若不定期透過重構償還,最終會把專案卡死。
對品質屬性的衝擊#
- 理解性:環中的抽象必須一起理解。
- 可變更性與可擴充性:改動會出現連鎖效應。
- 可重用性:環中的抽象只能整組重用。
- 可測試性:難以獨立測試。
- 可靠性:相依鏈中的型別變動可能在他處引發執行期錯誤(如 double → float 改型,他處才出現浮點誤差)。
別名#
- Dependency cycles、Cyclic dependencies、Cycles:類別有循環引用。
- Bidirectional relation:兩個類別的方法雙向呼叫。
- Cyclic class relationships:類別之間有共依賴等不當關係。
實務考量:單元環#
長度為 2 的環稱為 unit cycle,常出現在概念上密切相關的一對抽象之間。例如
java.util.regex.Matcher↔Pattern:兩者幾乎總是一起使用。 Unit cycles 因為小、易管理,通常可以接受。真正可怕的是大型循環與糾纏——這些是真實系統難維護的根源。
