整個繼承階層都不必要——繼承被誤用於不該用的場景時,就出現 Unnecessary Hierarchy。

核心定義#

「應用有意義的分類」要求我們依行為差異做分類。當差異實際上不在行為時,硬建出來的階層就是多餘的。

常見成因#

  • 以子類別取代實例RedRosePinkRoseWhiteRose 行為都一樣,差異只在顏色——應該用 Rose 加屬性。
  • 分類學狂熱(taxonomy mania):Meyer 的用語,過度迷信繼承。

範例#

範例 1:字型階層#

Figure 6.6: 文字編輯器中的 Font 類別階層(範例 1)

文字編輯器以 Font 為父類別,ArialFontCalibriFont 為子類別。但實際渲染由作業系統處理,子類別之間沒有行為差異——這個階層完全多餘。

範例 2:線條樣式的類別爆炸#

Figure 6.7: 繪圖程式中各種線條樣式與點線樣式

繪圖程式支援多種線條樣式:雙線、粗細線、細粗線、三線……以及各種點/虛線樣式。為每個組合建立子類別會立刻帶來:

  • 子類別與父類別沒有行為差異——實際渲染還是 OS 在做。
  • 類別爆炸:每加一個新樣式都得新增一群類別。

「分類有意義的前提,是子類別相較於父類別有更特化的行為。」—— Blaha & Rumbaugh

Figure 6.8: LineStyle 階層中的類別爆炸

案例:java.nio.Buffer 階層#

Figure 6.9: java.nio.Buffer 的部分階層

JDK 的 java.nio.Buffer 子類別正好對應 Java 的原始型別!原始碼其實是預處理器產生的——X-Buffer.java.template 一份模板。 之所以會有此重複,是因為 Java 泛型不支援原始型別(不能寫 List<int>),也不支援 C++ 那樣的 template specialization。

這仍是 Unnecessary Hierarchy;能否避免取決於語言支援

建議重構#

  • 子型別其實是父型別的「實例」 → 用屬性/enum 取代之。
  • 為常數而建的階層 → 改用 enum 或合適的語言特性。

Figure 6.10: 針對 Unnecessary Hierarchy 的建議重構

範例 1 的重構#

Figure 6.11: 為 Font 類別階層建議的重構

ArialFontCalibriFont 改為 Font 的物件實例,或乾脆用 enum。

範例 2 的重構#

Figure 6.12: 支援多種線條樣式的建議重構

抽出 LineStyleEnumDashDotStyleEnum 兩個 enum;Line 類別持有兩個 enum 值——新增樣式只要加 enum 常數,不會引發類別爆炸

對品質屬性的衝擊#

  • 理解性:多餘的階層讓設計複雜化。
  • 可擴充性:類別爆炸時擴充成本指數成長。
  • 可測試性:要為大量無實質差異的類別寫測試。

別名#

  • Taxomania:以繼承結構取代簡單的 boolean 屬性或固定值屬性。
  • Object classes:本應為實例卻被建為子類別。