本章主軸#

延續第 3 章的 CAD/CAM 問題,本章呈現最直覺的物件導向解法:把每一種特徵都做兩個版本(V1 與 V2 各一)。這個解法能跑、能交差,但作者刻意展示它為何是個壞設計——它正是「過度依賴繼承」帶來的典型反面教材。

解法的思路#

從 Slot 推廣到所有特徵#

作者推論:如果可以解 slot,那同一招就能套用到 cutout、hole 等其他特徵。

  • 為每個特徵建立一個抽象類別,例如 SlotFeatureHoleFeatureCutoutFeature
  • 針對 V1 與 V2 各做一個衍生類別:V1SlotV2SlotV1HoleV2Hole……
  • 五種特徵 × 兩個版本 = 10 個具象類別

Figure 4-3: 第一版解法——5 種特徵 × 2 個 CAD/CAM 版本造成的類別爆炸

各衍生類別的實作差異#

  • V1 系列:在物件中保存 model 編號與 ID;每次方法呼叫,就轉成一連串 V1 子程式呼叫
  • V2 系列:在物件中保存對應的 OOGFeature 物件參考,把請求委派給它
public class V1Slot extends SlotFeature {
    public double getX1() {
        return V1.getX1forSlot(myModelNumber, myID);
    }
}

public class V2Slot extends SlotFeature {
    public double getX1() throws Exception {
        return myOogF.getX1Loc();
    }
}

解法達成的目標#

解法達成了一個關鍵需求:對外暴露一致的 API,讓專家系統不必關心底層用的是 V1 或 V2。

注意:作者不需要在特徵層做多型,因為專家系統本來就要知道每個特徵的具體型別才能決定加工順序。真正需要多型的,是 V1/V2 這個變動軸。

為什麼這個解法「不夠好」#

四大問題#

  • 方法重複(redundancy):V1 系列的每個 getter 幾乎都長一個樣,只是呼叫不同 V1 子程式
  • 混亂(messy):類別圖鋪滿一堆 V?SlotV?Hole,凝聚的概念很弱
  • 緊密耦合(tight coupling):所有特徵都間接綁在 V1/V2 上,任何 CAD/CAM 變動都會牽連一整排類別
  • 弱凝聚(weak cohesion):核心職責散落在多個類別中

真正致命的:類別爆炸#

當 V3 推出時,原本 10 個類別會變成 15 個(5 個特徵 × 3 個版本)。

若再多一個變動軸(例如要支援不同單位制、不同精度),整個結構會以乘法膨脹,最終難以維護。

變動軸類別總數的成長方式
特徵類型 × 版本乘法(class explosion)
再加一個變動軸三維乘法,徹底失控

兩個應記取的設計教訓#

太早陷入細節是分析的陷阱#

細節最容易處理、解法也最明顯,因此分析師很容易掉進去。

愈晚決定細節,設計品質愈高。先找到問題的本質與變動軸,再決定如何切類別。

作者承認:他達成了「共同 API」這個目標,但代價是為一切都做特例化——每來一個特例就得新增一組類別。

注意你的直覺#

「直覺」雖然不科學,卻常常是設計品質最早的指標。

當你看到一個設計就感到不對勁,那種不舒服往往是真實的——更好的解法就在某個轉角,只是還沒找到。

作者表示,他第一眼就不喜歡這個解法,但花了兩小時仍找不到更好的。問題不在勤奮度,而在於整體思考方式——這部分要等讀完設計模式後才能突破。

本章重點#

  • 完全靠繼承來處理「多軸變動」會導致類別爆炸
  • 「能跑」不等於「可維護」;好用的 API 也可能背負糟糕的內部結構
  • 變動有不同來源時,不能用單一繼承樹同時表達——這正是後續引入 Bridge、Abstract Factory 等模式的原因
  • 設計時要練習聽自己的直覺,並學會延後對細節的承諾