當程式碼用**條件邏輯(switch、chained if-else)搭配「標籤型別」**處理行為的多樣性,而本應該以階層加封裝時,就出現 Missing Hierarchy。
核心定義#
最有名的設計壞味道之一就是「靠型別碼做 switch」。當把型別資訊編碼為 enum、整數或字串時:
- 行為差異未被妥善封裝——子型別的存在感為零。
- 共通性未被善用——通用部分散在各分支重寫。
- 同樣的 switch/if-else 會在多處重複,違反 DRY。
這也是 4.4 節 Unexploited Encapsulation 的鄰居——差別在於 Unexploited Encapsulation 階層已經存在但被繞開;Missing Hierarchy 則是階層根本不存在。
常見成因#
- 被誤導的「簡單」:以為 enum + 條件式比類別階層直接。
- 程序式取向:在 OO 語言中沿用 C 的型別編碼套路。
- 忽視繼承的價值:缺乏對 OO 機制的訓練。
範例#
範例 1:XPStyle.getBorderInsets#
if (c instanceof AbstractButton) {
margin = ((AbstractButton)c).getMargin();
} else if (c instanceof JToolBar) {
margin = ((JToolBar)c).getMargin();
} else if (c instanceof JTextComponent) {
margin = ((JTextComponent)c).getMargin();
}程式碼註解裡作者就坦承:「Ideally we’d have an interface defined for classes which support margins (to avoid this hackery), but we’ve decided against it for simplicity」——同樣的 if-else 在同一檔案重複了三次。
範例 2:AudioFormat.Encoding#
public static class Encoding {
public static final Encoding PCM_SIGNED = new Encoding("PCM_SIGNED");
public static final Encoding PCM_UNSIGNED = new Encoding("PCM_UNSIGNED");
public static final Encoding PCM_FLOAT = new Encoding("PCM_FLOAT");
public static final Encoding ULAW = new Encoding("ULAW");
public static final Encoding ALAW = new Encoding("ALAW");
private String name;
}客戶端到處出現比對:
if (targetEncoding.equals(Encoding.PCM_FLOAT)) bits = 32;
if (targetEncoding.equals(Encoding.PCM_SIGNED)) ...建議重構#
若條件分支內呼叫共通方法 → 引入介面包裝該協定。 若條件分支可以對應為類別 → 套用 extract hierarchy 並用多型替代條件式。
範例 1 的重構#

Figure 6.4: 為 AbstractButton、JTextComponent、JToolBar 建議的重構
引入 MarginSupported 介面,讓 AbstractButton、JToolBar、JTextComponent 實作:
if (c instanceof MarginSupported) {
margin = c.getMargin();
}理想上
getBorderInsets()應直接以MarginSupported為參數型別,但因為公開 API 不能改簽章,仍須保留instanceof MarginSupported。
範例 2 的重構#

Figure 6.5: 為音訊格式範例建議的重構
建立子類別 SignedPCMEncoding、UnsignedPCMEncoding、FloatPCMEncoding、ULAWEncoding、ALAWEncoding 繼承抽象的 Encoding,並把 getBitSize() 等方法交給多型分派:
bits = targetEncoding.getBitSize();對品質屬性的衝擊#
- 理解性:條件邏輯比多型多餘且難以掌握。
- 可變更性與可擴充性:每加一個型別都要改所有 switch。
- 可重用性:型別與行為被切離,整體難以重用。
- 可測試性:散落各處的條件式測試成本高。
- 可靠性:忘了某個 case → 執行期錯誤;型別碼用字串/整數時還缺驗證。
別名#
- Tag class:類別中含有「flavor」標籤欄位。
- Missing inheritance:以重複程式碼或 switch-case 取代繼承。
- Collapsed type hierarchy:類別中含有兩個以上的 switch/等價 if-else 來決定行為。
- Embedded features:類別屬性是「可選功能的開關」。
實務考量:與外部世界互動#
檔案格式(如
.class)會在資料中加上型別標記。讀取時必須以 switch 或 if-else 解碼。 同理,Object Serialization 中ObjectStreamField的型別碼(Z、B、C、S…)也是合理使用條件邏輯的場景。 資料一旦轉成物件後就不該再用 switch。