整理 Effective Java 書中 Item 22: Prefer interfaces to abstract classes 心得筆記

主旨

Java 的介面(interface)本質是用來定義「型別」的 —— 換句話說,介面要描述的是「這個物件可以做什麼」。如果你只是想要存一些常數,卻用介面來達成,那麼你就誤用了這個語言工具。

點出問題:介面不應該只是個「常數倉庫」

有些人會寫出這樣的程式碼,把常數全部塞進一個介面裡:

// 錯誤範例:把常數放在介面裡
public interface PhysicalConstants {
    static final double AVOGADROS_NUMBER = 6.022_140_857e23;
    static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
    static final double ELECTRON_MASS = 9.109_383_56e-31;
}

然後別的類別只為了使用這些常數,就去 implements PhysicalConstants。表面上看起來省事,但這麼做會讓本來應該封裝在內部的細節「曝光」到整個類別的對外介面(API)中,造成以下問題:

  • 類別的 API 被污染,看起來像支援某些行為,但其實只是用了幾個常數。
  • 無法輕易移除介面,因為子類別也會繼承那些不該外露的常數名稱。
  • 如果未來那組常數不再需要,你也不能輕易把 implements 拿掉,否則會破壞相容性。

替代作法:用工具類別或 enum 替代

若你真的需要共享一組常數,可以這樣做:

// 建立 utility class 來放常數
public class PhysicalConstants {
    private PhysicalConstants() {} // 阻止實例化

    public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
    public static final double BOLTZMANN_CONST = 1.380_648_52e-23;
    public static final double ELECTRON_MASS = 9.109_383_56e-31;
}

這種寫法有以下優點:

  • 常數與邏輯清楚分離。
  • 不會強迫子類別繼承無意義的欄位。
  • 可以自由選擇是否透過 static import 引入常數名稱。

如果你的常數屬於某種類別的列舉(例如行星、貨幣單位),則可以考慮使用 enum,讓語意更清晰(參考 Item 34)。

劃重點

  • 介面是用來定義「型別」行為,不是存資料用的。
  • 避免使用「常數介面」(constant interface)反模式。
  • 常數應放在 utility class 或 enum 裡面。
  • 如需方便使用可用 static import,但不要為此破壞型別設計。

真實世界的例子

Java 本身也犯過這個錯。例如 java.io.ObjectStreamConstants 是個典型的常數介面。它現在仍然存在,但已被視為歷史遺留問題(legacy),官方不建議效仿。

小結

介面應該用來「定義你能怎麼跟物件互動」,而不是「你要用什麼數字」。混淆這兩者只會讓程式架構混亂、維護變難。若只是為了省打幾個字而實作常數介面,將來很可能會後悔。保持介面的純粹性,未來你和你的團隊會感謝自己。