當階層中存在不必要的重複——共通的介面或實作沒有被抽到父型別——就出現 Unfactored Hierarchy。
核心定義#
兩種重複形式:
- 兄弟型別之間的重複:應抽到共同父型別。
- 父子之間的重複:子型別中存在多餘地重新定義。
具體呈現為:
- 未抽出的介面:兄弟型別有相同的方法簽章。
- 未抽出的實作:兄弟型別有相同的方法實作。
- 介面與實作都未抽出:兩者皆同。
此壞味道違反 DRY ↗ 原則與「應用有意義的泛化」這個使能技巧。
常見成因#
- 複製子型別:「複製貼上 + 改名」是最快的方法,但也是最快引入此壞味道的方式。
- 不當處理離群子型別(outliers):典型例子是
Bird階層加入Penguin:fly()留在Bird父類 →Penguin.fly()必須以例外或空實作拒絕 → Rebellious Hierarchy(6.7 節)。- 把
fly()從Bird拿掉、所有會飛的子類別重新各自定義 → Unfactored Hierarchy。 - 真正乾淨的解:引入
FlyingBird與NonFlyingBird中介類別,將fly()留在FlyingBird。
範例#
範例 1:NumberFormat(介面未抽出)#

Figure 6.13: NumberFormat 類別階層
java.text.NumberFormat 的子類 ChoiceFormat、DecimalFormat 都實作 applyPattern() 與 toPattern(),簽章相同、實作不同。應在父類別中宣告為抽象方法。
範例 2:AbstractQueuedSynchronizer(實作未抽出)#

Figure 6.14: AbstractOwnableSynchronizer 子型別中未被抽出的方法
AbstractQueuedSynchronizer 與 AbstractQueuedLongSynchronizer 都繼承自 AbstractOwnableSynchronizer,2110 行裡有 1278 行完全重複!原始碼註解直白地寫:
“To keep sources in sync, the remainder of this source file is exactly cloned from AbstractQueuedSynchronizer, replacing class name and changing ints related with sync state to longs. Please keep it that way.”
這是 Java 泛型不支援原始型別的副作用。但有些方法簽章其實相同(如
Collection<Thread> getExclusiveQueuedThreads()),這些可以也應該被抽到父類別。
範例 3:Rectangle 的 outcode()#

Figure 6.15: java.awt.Rectangle 的部分繼承階層
java.awt.Rectangle 繼承 java.awt.geom.Rectangle2D。兩者的 outcode() 方法程式碼完全相同,連註解都只有一句之差!子類別根本不需重新定義。
建議重構#
套用「factoring」原則:將兄弟之間的共通介面、實作、欄位pull-up 到父型別。
- 範例 1:在
NumberFormat中宣告applyPattern()、toPattern()為抽象方法。

Figure 6.16: 為 NumberFormat 階層建議的重構
- 範例 2:把所有完全相同的方法 pull up 到
AbstractOwnableSynchronizer。

Figure 6.17: 為 AbstractOwnableSynchronizer 建議的重構
- 範例 3:直接刪除
Rectangle.outcode()的多餘定義(會自動繼承父類)。
若兄弟方法步驟順序相同但細節不同,套用 Template Method pattern ↗。
軼事:Unfactored Hierarchy 與 instanceof#
一個近百萬行的 Java 應用程式,工具掃描出大量
instanceof檢查。深入分析後發現:兄弟類別中有許多介面和實作其實重複未抽出——客戶端只能拿到父型別參考,被迫做 instanceof 與 downcast 才能呼叫子型別方法。 重構把共通介面 pull up 後,多數 instanceof 也跟著消失。
教訓:
- 工具偵測 + 人工分析才是辨識壞味道的有效組合。
- 壞味道彼此相關——一個壞味道往往連動其他壞味道。
對品質屬性的衝擊#
- 理解性:重複膨脹程式碼、增加認知負擔。
- 可變更性與可靠性:共通邏輯改動時要動所有副本,少改一處就出 bug。
- 可擴充性:新加子類別還要再複製一次。
- 可重用性:未抽出共通介面 → 客戶端無法善用多型。
- 可測試性:複製的程式碼要重複測。
別名#
- Orphan sibling method/attribute:成員出現在多數子類別中、卻未在祖先中定義。
- Incomplete inheritance、Repeated functionality、Significant sibling duplication:兄弟之間有大量共通程式碼。
- Redundant variable declaration:子類別有與父類別相同的資料。
實務考量:語言支援不足#
抽出共通實作後,若該實作不適合所有子型別 → 變成 Rebellious Hierarchy。Java/C# 不支援多重類別繼承,迴避空間有限。 此外,Java 泛型不支援原始型別,因此
int與long版本的同邏輯類別不得不複製。這是語言限制下的合理妥協。