當設計中引入了原本可避免的抽象——責任瑣碎、甚至完全沒有責任——就出現 Unnecessary Abstraction 壞味道。

核心定義#

「指派單一且有意義的責任」是抽象的使能技巧之一。當抽象只是為了「方便」或「為了建而建」而存在,就違反這個技巧。

常見成因#

  • OO 中的程序式思維:類別淪為「動詞 + 資料」的拼裝。
  • 為了便利而濫用語言特性:例如以「常數介面(constant interface)」取代列舉,省掉每次以型別名稱限定的麻煩。
  • 過度設計(over-engineering):為了單純的資料元素硬建類別,例如為 Customer 中的 customer ID 建立 CustomerID 類別。

範例#

範例 1:FormattableFlags 的常數類別#

java.util.FormattableFlags(JDK 7)只用來放三個整數旗標:

public class FormattableFlags {
    private FormattableFlags() {}
    public static final int LEFT_JUSTIFY = 1<<0;
    public static final int UPPERCASE = 1<<1;
    public static final int ALTERNATE = 1<<2;
}

此類別在 Java 1.5 引入時,列舉(enum)已經存在——應該直接使用 enum。

範例 2:常數介面#

javax.swing.WindowConstants 將四個視窗關閉操作的常數定義在介面裡:

public interface WindowConstants {
    public static final int DO_NOTHING_ON_CLOSE = 0;
    public static final int HIDE_ON_CLOSE = 1;
    public static final int DISPOSE_ON_CLOSE = 2;
    public static final int EXIT_ON_CLOSE = 3;
}

JFrameJInternalFrameJDialog 等類別繼承此介面,只為了方便存取常數。書中強烈反對這種做法:

  • 子類別被「汙染」上不相關的常數。
  • 常數本是實作細節,透過介面公開等於違反封裝。
  • 介面中的常數一改,所有客戶端都會壞

介面的本意是「實作者必須遵守的協定」。把它當常數容器是對抽象機制的濫用

範例 3:BestSellerBook 的純委派#

電商系統建立 BestSellerBook 類別,但它所有方法都委派給 Book,沒有額外行為——這個抽象沒理由存在。

建議重構#

對自己付不出代價的類別應該被消滅。」(Fowler)

  • 套用 inline class 重構,將該類別合併進另一個類別。
  • 若用類別/介面承載常數,改用 enum。
  • 範例 1:將 FormattableFlags 變為 enum,搭配 java.util.Formatter 使用。
  • 範例 2:將 WindowConstants 改為 enum;ObjectStreamConstants 同理。
  • 範例 3:刪除 BestSellerBook,在 Book 中加上 isBestSeller 屬性與其 getter/setter。

對品質屬性的衝擊#

  • 理解性:多餘的抽象增加認知負擔。
  • 可重用性:責任瑣碎或無責任的抽象難在新脈絡中重用。

別名#

  • Irrelevant class:類別在設計中無有意義的行為。
  • Lazy class/Freeloader:類別做的事「太少」。
  • Small classMini-class:方法/欄位過少。
  • No responsibility:類別沒有任何責任。
  • Agent classes:類別只是訊息傳遞者。

實務考量#

設計樣式中的委派類別#

Mediator、Proxy、Façade、Adapter 等樣式可能讓類別「看起來在純委派」。但這些類別有清楚明確的責任(如 Adapter 把 Adaptee 的介面轉成客戶需要的介面),不算 Unnecessary Abstraction。

容納變化#

java.lang.Mathjava.lang.StrictMath 都提供類似的數學函式:

  • Math 為了效能,可能用原生硬體浮點。
  • StrictMath 嚴格遵守浮點標準,跨平台結果一致
  • 兩者並存是有意識的設計選擇——以兩個抽象容納可攜性 vs. 效能的權衡,不是壞味道