當父型別直接或間接依賴自己的子型別時,就出現 Cyclic Hierarchy。
核心定義#
依賴形態包含:
- 父型別內含一個子型別物件。
- 父型別引用子型別的型別名稱。
- 父型別存取子型別的資料或呼叫子型別的方法。
自然順序是「子型別依賴父型別」。父型別反過來依賴子型別違反了「確保適當的順序」與 ADP(Acyclic Dependencies Principle)。
特別危險的形態:父型別的建構子主動使用某個子型別。建構父類別時子類別還未初始化,會接觸到未初始化的部分——隱蔽的 bug。
常見成因#
- 責任指派錯誤:本該屬於子型別的責任被指派到父型別。
- 間接相依難察覺:在大型階層中,父型別經由多層中介最後依賴回自己的子型別。
範例:AbstractButton ↔ JButton#
AbstractButton 透過 SwingUtilities → JRootPane → JButton 形成依賴鏈:
flowchart LR
A[AbstractButton] --> B[SwingUtilities]
B --> C[JRootPane]
C --> D[JButton]
D --> AAbstractButton 是 JButton 的父類別——這條間接路徑形成 Cyclic Hierarchy。

Figure 6.48: AbstractButton 與 JButton 之間的 Cyclic Hierarchy 壞味道
建議重構#
- 不必要的引用 → 移除。
- 父子緊耦合且本質上是同一抽象 → 合併。
- move method、extract class 等手法搬出造成循環的程式碼。
- 父型別需要子型別的「狀態切換」或「演算法切換」 → 套用 State ↗ 或 Strategy ↗ 樣式。
flowchart LR
Parent[父型別] -->|改用 State / Strategy| Context[Context]
Context --> SH[State / Strategy 階層]
Figure 6.49: State 設計樣式的結構

Figure 6.50: Strategy 設計樣式的結構
範例的重構#
把 SwingUtilities 拆分——將 getRootPane() 移到新類別 ComponentUtilities。AbstractButton 不呼叫 getRootPane(),所以它不會引用 ComponentUtilities → 鏈被打破。

Figure 6.51: AbstractButton 與 JButton 之間 Cyclic Hierarchy 的重構
對品質屬性的衝擊#
- 理解性:父型別必須與子型別一起被理解;自然順序被打破。
- 可變更性、可擴充性、可靠性:子型別變動波及父型別,再回頭波及其他子型別——常以執行期錯誤呈現。
- 可重用性:父型別不能獨立使用。
- 可測試性:父型別不能獨立測試。
別名#
- Knows of derived:父類別/介面在編譯期相依於某個直接或間接後代。
- Curious superclasses:父類別呼叫或包含子類別實例。
- Inheritance/reference cycles:父類別引用自己的子類別。
- Descendant reference:類別透過 association、attribute、parameter 引用後代類別。
- Superclass uses subclass during initialization:父類別建構期間主動使用子類別(後者尚未初始化)。
- Inheritance loops:A→B→C→A 形成繼承迴圈。
實務考量:穩定的子型別#
當子型別未來不會變更時,父型別依賴子型別的問題就被緩和了——可作為設計妥協的依據。