一個子型別繼承了父型別,卻在覆寫時「造反」——某些父方法它拋
UnsupportedOperationException、給空實作、或回傳錯誤值來表示「我不支援」。IS-A 關係看似成立,替換卻會爆炸。這是 Rebellious Hierarchy(叛逆繼承 / Refused Bequest),怎麼辨識、怎麼解?
🎯 Scenario
open class CallSite {
open fun setTarget(t: Target) { /* 可變更目標 */ }
fun getTarget(): Target = TODO()
}
// 常數呼叫點:target 永久不變 → 被迫拒絕父類別的 setTarget
class ConstantCallSite : CallSite() {
override fun setTarget(t: Target): Unit =
throw UnsupportedOperationException("constant call site") // 造反!
}
壞味道:Rebellious Hierarchy——子型別拒絕繼承來的方法(拋例外 / 空實作 / 印 “should not implement” / 回傳錯誤值)。直接違反 里氏替換原則:拿子型別替換父型別會失敗。
🧠 Intuition
TIP
先別看下面。問兩個問題:(1) 子型別和父型別真的是 IS-A 嗎? (2) 被拒絕的方法,是只有部分子型別不需要,還是全部子型別都不需要? 這兩題的答案決定了該往哪個方向解。
⚖️ Reference Design
先分辨:Rebellious 還是 Broken?
- Rebellious Hierarchy:父子有 IS-A 關係,但子型別拒絕某些方法(本頁)。
- Broken Hierarchy:父子根本沒有 IS-A 關係,繼承本身就錯了(用了繼承只為重用程式碼)。
- 別名:Refused bequest(拒絕遺贈)、Naughty children、Premature interface abstraction(方法在階層中位置太高,被部分子型別拒絕)。
解法:看『誰不需要這方法』決定往哪搬
// 方案:把可變更目標的能力下放到中介型別,常數呼叫點就不必再拒絕
open class CallSite { fun getTarget(): Target = TODO() }
open class ChangeableCallSite : CallSite() { open fun setTarget(t: Target) { /* ... */ } }
class ConstantCallSite : CallSite() // 不再有 setTarget,無從拒絕
class MutableCallSite : ChangeableCallSite() // 需要才繼承可變能力- 被拒方法只與部分子型別相關 → move method 把它下放到那些子型別,或引入中介型別容納共通子集。
- 被拒方法所有子型別都不需要 → 直接從父型別 remove method。
- 成因常是 Swiss-knife 父類別(把所有可能功能塞進父類,逼子類拒絕)——這也是 ISP 要解的:介面別胖到讓實作者被迫拒絕。
- 若父子其實不是 IS-A,改用組合委派,而非繼承。
NOTE
書中提醒:真實設計常沒有完美解(每個方案都有代價,如變成 Incomplete/Unfactored Hierarchy),要在權衡下選。
務實例外:刻意引入的『造反』
書中列了幾個有意違反、且被明確記錄的合理情況:
- 尚未實作:
RTFEditorKit.write()拋例外,因為 RTF 支援是 work-in-progress,拒絕只是暫時占位。 - 為效能:
DefaultListCellRenderer把repaint()/validate()覆寫成 NOP,避免無謂成本。 - 為避免類別爆炸:
UnmodifiableList拒絕add()/remove()——違反 LSP,但是 Collections API 的有意取捨。 - Composite 樣式:讓 client 透明處理 leaf 與 composite 時,leaf 的
addComponent()無意義,三種解法都有代價,設計者有意識地選擇承受此味道。
🔑 Takeaways
- Rebellious Hierarchy =子型別拒絕繼承來的方法(拋例外/空實作/錯誤值),違反 LSP。
- 先分辨是 Rebellious(有 IS-A、拒方法)還是 Broken(根本不該繼承);成因常是 Swiss-knife 父類(該用 ISP 收斂)。
- 解法看「誰不需要該方法」:部分不需要 → move/中介型別;全部不需要 → remove;不是 IS-A → 改組合。
- 少數刻意違反(未實作、效能、避免類別爆炸、Composite 透明性)是合理權衡,但要明確記錄。
No notes yet — jot your takeaways or Q&A here.