當父型別與子型別在概念上根本沒有 IS-A 關係——替換性被破壞——就出現 Broken Hierarchy。

核心定義#

三種形態:

  • Form 1:父類方法仍適用於子類別(雖然概念上沒有 IS-A 關係)。
  • Form 2:子類別繼承的方法不適用,但子類別沒有拒絕它們。
  • Form 3:子類別明確拒絕繼承來的不相關方法。

極端形式:繼承關係反向——子型別反而是父型別的泛化。

與 Rebellious Hierarchy 的關鍵差別:Broken Hierarchy 連 IS-A 都不成立;Rebellious Hierarchy 是 IS-A 成立但部分方法被拒絕。

常見成因#

  • 為了重用實作而繼承:「我只想用父類別的程式碼,但事實上不是它的特化」。
  • 錯誤套用繼承:把 IS-A 與其他關係混淆,甚至把繼承關係反向

範例#

範例 1:JobStateReasons extends HashSet(Form 1)#

javax.print.attribute.standard.JobStateReasons 繼承 HashSet,文件直白地說「inherits its implementation from class java.util.HashSet」——為實作重用而繼承,並非真正的 IS-A

Figure 6.33: JobStateReasons 的部分繼承階層

範例 2:Stack extends Vector(Form 2)#

java.util.Stack 繼承 java.util.Vector

Stack 不是 Vector!Stack 應為 LIFO,但 Vector 允許從中間插入或移除——而 Stack 子類沒有拒絕這些方法。可以對 stack 做違反語意的操作!

Figure 6.34: Stack 類別的部分繼承階層

範例 3:Properties extends Hashtable(Form 2)#

java.util.Properties 鍵值都應為 String,但 extends Hashtable<Object, Object>。文件警告:

putputAll 強烈不建議使用,會插入非字串鍵值。storesave 在被汙染的 Properties 物件上會失敗。」

Figure 6.35: Properties 類別的部分繼承階層

範例 4:java.sql.Date / java.sql.Time(Form 3)#

兩者都繼承 java.util.Date,但:

  • java.sql.Date 拒絕所有時間相關方法。
  • java.sql.Time 拒絕所有日期相關方法。
java.util.Date date = new java.util.Date();
date = new java.sql.Time(10, 10, 10);
date.getDate(); // 拋 IllegalArgumentException

此範例同時觸發 Duplicate Abstraction(同名 Date)與 Multifaceted Abstraction(java.util.Date 同時管日期與時間)。

Figure 6.36: java.util.Date 的部分階層

範例 5:Employee extends Manager(反向繼承)#

新手以為「員工是經理的特化」(因為員工要回報給經理),把 Employee 寫成 Manager 的子類。但 getReportees()conductPerformanceEvaluation() 等方法對非經理員工毫無意義——只能拒絕或留空。

真正的關係應該是 Manager IS-A Employee——繼承方向相反。

Figure 6.37: 反向繼承階層的範例

案例:BorderCell extends Cell#

一個自訂 GUI 控制元件中,BorderCell 看似 Cell 的特化(有邊框)。後來發現原始碼裡 BorderCell 其實是一群 cells 的容器——名字騙人。

教訓:糟糕的命名能讓設計審查也漏掉問題

建議重構#

對 Form 1–3:套用「replace inheritance with delegation」(HAS-A 取代 IS-A)。

  • 範例 2Stack 內部持有 Vector,不再繼承之。

Figure 6.38: Stack 改以委派 Vector 取代繼承的重構

  • 範例 5(反向繼承):將繼承方向反過來——Manager extends Employee

Figure 6.39: 對反向繼承階層建議的重構

對品質屬性的衝擊#

  • 理解性:強加的 IS-A 讓設計違反直覺。
  • 可重用性、可變更性、可擴充性:替換性破洞使客戶端無法仰賴父型別撰寫程式碼。
  • 可靠性:客戶端不知 IS-A 已破,把子型別物件指派給父型別參考時可能引發例外。

別名#

  • Inappropriate use of inheritance:將不是 IS-A 的關係建為繼承。
  • Containment by inheritance:繼承關係不代表合理的特化。
  • Mistaken aggregates:繼承與物件組合混淆。
  • Misapplying IS A:把屬性誤認為基底型別。
  • Improper inheritance:用繼承代替實例化。
  • Inverse abstraction hierarchies:子類別比父類別更抽象。
  • Subclasses do not redefine methods:純實作繼承。
  • Subclass inheriting inappropriate operations from superclass:本應該以訊息轉發處理。

實務考量:Class Adapter 樣式#

Adapter pattern 有兩種變體:

  • Class Adapter:透過繼承轉換介面——常引入 Broken Hierarchy。
  • Object Adapter:透過委派轉換介面——避免 Broken Hierarchy。

在某些情境(覆寫 Adaptee 行為、減少物件數、消除指標間接性)下,設計者仍可能有意選擇 Class Adapter

Figure 6.40: Class Adapter 樣式的結構

Figure 6.41: Object Adapter 樣式的結構