abstract class 與 interface 是 Java 支援抽象類型定義的兩大機制,賦予了 Java 強大的面向對象能力。雖然兩者在語法層面可相互替換,但選擇背後反映了對問題領域本質的理解、設計意圖的正確性與合理性。本文從語法定義、程式設計層面、設計哲學三個維度深度剖析,提供選型決策依據。
在面向對象領域,並非所有類別都能描述具體物件。當類別缺乏足夠資訊描述具體對象時,即為抽象類型。它表徵問題領域分析設計中提煉出的抽象概念——對一系列外表不同但本質相同具體概念的抽象。例如圖形編輯軟體中「圓」、「三角形」屬於不同具體概念,但同屬「形狀」抽象概念。因抽象概念在問題領域無對應具體實例,故抽象類型不可實例化。
抽象類型的核心價值在於型別隱藏:構造固定行為組合的抽象描述,允許任意數量具體實現。模組依賴固定抽象體而不變,透過衍生擴展行為,正是實現開放封閉原則(OCP)的關鍵所在。
// abstract class 方式
abstract class Demo {
abstract void method1();
abstract void method2();
// 可擁有資料成員、非 abstract 方法
}
// interface 方式
interface Demo {
void method1();
void method2();
// 僅靜態常數(static final),所有方法隱含 abstract
}
從語法觀點,interface 可視為特殊形式的 abstract class。兩者皆能實現「design by contract」,但具體使用差異顯著:
| 維度 | abstract class | interface |
|---|---|---|
| 繼承關係 | 單一繼承(is-a) | 多重實作(like-a) |
| 預設行為 | 可定義非抽象方法 | Java 8 前不可,僅靜態/預設方法 |
| 狀態儲存 | 可擁有實例欄位 | 僅 static final 常數 |
| 建構子 | 可定義 | 不可定義 |
介面無法定義預設行為(Java 8 前),導致兩大維護隱患:
抽象類可在基類定義預設行為,僅需修改一處即可全域生效。選型時必須權衡此維護成本。
語法差異屬表象,設計哲學才是核心。abstract class 體現 is-a(繼承)關係,父子類在概念本質上必須相同;interface 體現 like-a(契約)關係,實作者僅需滿足契約,無須本質一致。
假設領域有 Door 抽象概念(open/close),現需增加報警功能。
abstract class Door { // 或 interface Door
abstract void open();
abstract void close();
abstract void alarm(); // 混入「報警器」概念
}
違反介面隔離原則(ISP):依賴 Door 的模組因 Alarm 變更而受影響,反之亦然。
Java 不支援多重繼承,故雙 abstract class 不可行。剩餘兩種:
interface Door + interface Alarm
abstract class Door { // 核心身份:is-a Door
abstract void open();
abstract void close();
}
interface Alarm { // 附加能力:like-a Alarm
void alarm();
}
class AlarmDoor extends Door implements Alarm { ... }
明確表達:AlarmDoor 本質是 Door,同時具備 Alarm 能力。若本質是 Alarm 則反過來定義。
| 場景 | 推薦選擇 | 理由 |
|---|---|---|
| 核心領域模型,強 is-a 語義 | abstract class | 表達本質身份,支援預設行為與狀態 |
| 跨領域能力、混入特性 | interface | 表達 like-a 契約,支援多重實作 |
| 需提供預設實現、共享狀態 | abstract class | 避免代碼重複,集中維護 |
| API 穩定、長期演進 | interface(Java 8+ default) | 二進位相容,不破壞既有實作 |
| 框架擴展點、SPI | interface | 鬆耦合,不強制繼承體系 |
abstract class 與 interface 雖相似,但選擇反映了對問題領域概念本質的理解與設計意圖的表達。前者表達「是什麼」(is-a),後者表達「能做什麼」(like-a)。正確選型不僅滿足功能需求,更能讓代碼成為清晰的領域模型文檔,支援系統長期演進。這正是程式語言慣用法的核心價值所在。