「面向介面編程」早已成為共識,但許多開發者仍在具體場景下猶豫:該用繼承還是介面?本文從語言機制、設計哲學、演進趨勢、實戰決策矩陣四個維度,徹底講透這對關係。
| 維度 | 繼承 | 介面 |
|---|---|---|
| 語義 | IS-A (是一種) | CAN-DO / HAS-ABILITY (能做什麼) |
| 耦合度 | 強耦合:子類繼承父類所有實現細節 | 弱耦合:僅依賴契約,實現可隨時替換 |
| 複用方式 | 實現繼承 | 契約規範,組合/委派複用實現 |
| 擴展性 | 單繼承限制,易導致類爆炸 | 多實現,靈活組合 |
| 破壞性 | 父類修改波及所有子類 | 介面不變,實現自由變更 |
需求:狗需要「爬樹」能力。
class Dog extends Monkey → 狗獲得爬樹,但也獲得「尾巴倒掛」、「飛簷走壁」等不需要甚至有害的行為。interface Climable { void climb(); } → class Dog implements Climable → 僅獲得所需能力,零副作用。這就是 介面隔離原則 (ISP) 的生動體現:客戶不應依賴它不需要的方法。
Vector/Stack、Hashtable、Dictionary、AWT 元件繼承樹Collection/List/Set/Map 介面,ArrayList/LinkedList 實現介面,AbstractList 提供骨架實現default 方法讓介面攜帶默認實現,FunctionalInterface 支援 Lambda,Sealed Interface (JDK 17) 可控實現範圍 —— 介面已成主流抽象工具| 場景 | 推薦 | 理由 |
|---|---|---|
| 共享核心邏輯/狀態,且語義確為 IS-A | 繼承 (配合 abstract class) | 如 AbstractList 提供 size/modCount 等通用實現 |
| 定義能力契約,多種實現並存 | 介面 | 如 Comparator、Runnable、Serializable |
| 需要多重能力組合 | 介面 | Java 單繼承,只能靠多實現組合 |
| API 公開給第三方實現/擴展 | 介面 | 保護內部實現細節,維持二進制兼容 |
| 骨架實現 + 模板方法模式 | abstract class + 介面 | 介面定義契約,抽象類提供骨架,具體類二選一實現 |
// 1. 定義契約
public interface Repository {
Optional findById(ID id);
List findAll();
T save(T entity);
void deleteById(ID id);
}
// 2. 提供骨架實現 (可選)
public abstract class AbstractRepository implements Repository {
@Override public List findAll() {
// 通用邏輯:緩存、審計、事務邊界
return doFindAll();
}
protected abstract List doFindAll();
}
// 3. 具體實現自由選擇
@Repository
public class JpaUserRepository extends AbstractRepository
implements UserRepository { // 可再實現擴展介面
// ...
}
@Service
public class UserService {
private final UserRepository repo; // 依賴介面,而非具體類
// 構造器注入,可測試、可替換、可AOP
}
繼承是「白箱複用」,介面是「黑箱契約」。現代 Java 設計:預設用介面定義契約,需共享實現時引入抽象類,具體類通過組合/委派複用邏輯。這樣既保持鬆耦合,又避免代碼重複,兼顧可測試性與演進彈性。