當子型別**「拒絕」**了從父型別繼承的方法時,就出現 Rebellious Hierarchy。
核心定義#
子型別與父型別在概念上仍是 IS-A 關係,但子型別覆寫父方法的方式違反了這份契約:
- 拋例外拒絕呼叫。
- 提供空(NOP)實作。
- 印出「should not implement」訊息。
- 回傳錯誤值表示不支援。
與 6.8 節 Broken Hierarchy 的差別:
- Rebellious Hierarchy:父子有 IS-A 關係,但子型別拒絕某些方法。
- Broken Hierarchy:父子沒有 IS-A 關係,繼承本身就錯了。
常見成因#
- 由現有具體類別衍生新階層:原本的具體類別不適合做父型別。
- Swiss-knife 父類別:設計者把所有可能的功能塞進父類別,子類別被迫拒絕不相關的方法。
範例#
範例 1:CallSite#
java.lang.invoke.CallSite 的子類:
ConstantCallSite:target 永久不變 →setTarget()拋UnsupportedOperationException。MutableCallSite、VolatileCallSite:target 可變。
結果父類別的
setTarget()在某個子型別中無效。

Figure 6.26: java.lang.invoke.CallSite 的繼承階層
範例 2:Toolkit / HeadlessToolkit#
java.awt.Toolkit 的子類 HeadlessToolkit 沒有顯示器、鍵盤、滑鼠——大量方法(createWindow()、getGlobalCursorManager() 等)拋 HeadlessException。
範例 3:Iterator.remove()#
java.util.Iterator 的 remove() 在 java.util.Scanner 中被拋 UnsupportedOperationException 拒絕——因為 Scanner 是 read-only 的 tokenizer。
建議重構#
- 被拒絕的方法只與部分子型別相關 → move method 把方法搬到那些子型別。
- 被拒絕的方法所有子型別都不需要 → remove method 從父型別移除。
- 也可引入中介型別容納共通子集的需求。
範例 1:CallSite 的三種方案#
| 選項 | 做法 | 代價 |
|---|---|---|
| 1 | 從 CallSite 移除 setTarget() | CallSite 變成 Incomplete Abstraction |
| 2 | 把兩個方法都搬到子類別 | 出現 Unfactored Hierarchy |
| 3 | 引入 ChangeableCallSite 中介類別,將 setTarget() 放入 | getTarget() 仍在子類重複 |

Figure 6.27: 對 CallSite 建議的重構(選項 1)

Figure 6.28: 對 CallSite 建議的重構(選項 2)

Figure 6.29: 對 CallSite 建議的重構(選項 3)
真實設計裡常常沒有完美解——必須在多種權衡下選擇。
範例 2:Toolkit 的重構#
把 Toolkit 拆為 Toolkit(無設備相關方法)→ DefaultToolkit(含設備方法)/HeadlessToolkit(純 headless)。

Figure 6.30: 對 Toolkit 階層建議的重構
範例 3:Iterator 的重構#
引入 ReadOnlyIterator(不含 remove())。java.util.Scanner 改實作 ReadOnlyIterator。
對品質屬性的衝擊#
- 理解性:客戶端會被「替換失敗」嚇到。
- 可變更性:客戶端被迫綁定具體子型別。
- 可擴充性:新加子類別也必須拒絕同樣方法。
- 可重用性:行為不一致使重用困難。
- 可靠性:拋出未處理的例外可能讓應用程式崩潰。
別名#
- Refused bequest:拋例外、空實作、或客戶端只用子類引用。
- Refused parent bequest:私有繼承介面方法或以空方法覆寫。
- Naughty children:子類別不接受父類別接受的所有訊息。
- Premature interface abstraction:方法在階層中位置太高,被部分具體子類拒絕。
實務考量:刻意引入的 Rebellious Hierarchy#
書中提供三個有意違反此原則的合理例子:
尚未實作的功能#
RTFEditorKit.write()拋IOException,因為 RTF 支援是「work-in-progress」。文件直接坦承「The RTF support was not written by the Swing team. In the future we hope to improve」——拒絕是暫時的占位。

Figure 6.31: RTFEditorKit 的繼承階層
為效能而覆寫為 NOP#
DefaultListCellRenderer等覆寫invalidate()、validate()、repaint()為空方法,是為了避免無謂的執行成本。設計者明確記錄此決策。
為避免類別爆炸#
JDK 7 的
UnmodifiableList、UnmodifiableMap拒絕add()、set()、remove()——違反 LSP。但這是 Java Collections API 為避免介面與類別爆炸做的有意取捨。
案例:Composite 樣式#
Composite pattern
↗ 的關鍵在於讓 client 透明地處理 leaf 與 composite。父類別 Component 中的 addComponent() 對 leaf 無意義,三種解法都有代價:

Figure 6.32: Composite 設計樣式的結構
- 在 leaf 中拒絕該方法 → Rebellious Hierarchy。
- 把
addComponent()移到Composite→ 客戶端必須做型別檢查 → 失去透明性。 - 在
Component中提供 NOP 預設實作 → 不會壞,但 bug 會被吞掉。
設計者有意識地引入此壞味道,以保留樣式的核心價值。