James Gosling 曾言:「若重設計 Java,我會拋棄 class,只保留 interface。」本文深度剖析實現繼承 (extends) 的三大核心缺陷——靈活性喪失、緊耦合、脆弱基類問題,並展示面向接口編程如何構建彈性架構。
implements (接口繼承) 定義「契約」;extends (實現繼承) 耦合「實現」// 緊耦合 LinkedList
void process(LinkedList list) {
list.add("item");
anotherMethod(list); // 傳遞具體類型
}
// 需求變更:改用 HashSet 以支持 O(1) 查找
// 必須修改 process、anotherMethod 及所有調用鏈簽名 → 災難性擴散
// 僅依賴 Collection 契約
void process(Collection collection) {
collection.add("item");
anotherMethod(collection);
}
// 切換實現:僅修改構造處
Collection c = new HashSet(); // 或 new LinkedList(), new ArrayList()
process(c); // 零修改其他代碼
void process(Iterator iterator) {
while (iterator.hasNext()) {
doSomething(iterator.next());
}
}
// 可傳入:集合迭代器、數據庫游標、文件讀取器、測試數據生成器...
// 極致解耦:方法完全不知數據來源
繼承創建了「is-a」關係,導致子類不可避免依賴父類實現細節:
private 字段變更 → 子類可能破壞 (反射/繼承鏈)// 父類
public class Base {
protected int state; // 違反封裝:子類直接訪問字段
public void setState(int s) { state = s; }
}
// 子類
public class Derived extends Base {
public void risky() {
state = -1; // 繞過驗證邏輯,破壞父類不變約束
}
}
鐵律:實例字段必須 private,提供受控訪問器;protected 僅用於模板方法模式的鉤子方法,絕不暴露字段。
public class BrokenStack extends ArrayList {
private int pointer = 0;
public void push(Object o) {
add(pointer++, o);
}
public Object pop() {
return remove(--pointer);
}
}
// 客戶端代碼
BrokenStack stack = new BrokenStack();
stack.push("A");
stack.push("B");
stack.clear(); // 繼承自 ArrayList,pointer 不重置!
stack.push("C"); // 指針仍為 2,數據錯位,底部殘留垃圾對象
clear, remove, add, set...) 同步 pointerUnsupportedOperationException → 編譯期錯誤變運行期錯誤BrokenStack 不能安全替換 ArrayListpublic class SafeStack {
private final List list = new ArrayList(); // 內部委託
private int pointer = 0;
public void push(Object o) {
list.add(pointer++, o);
}
public Object pop() {
return list.remove(--pointer);
}
// 僅暴露棧語義方法,完全隱藏 List 能力
public int size() { return pointer; }
public boolean isEmpty() { return pointer == 0; }
}
public class MonitoredStack extends SafeStack { // 若 SafeStack 用繼承實現
private int highWater = 0;
@Override
public void push(Object o) {
if (++pointer > highWater) highWater = pointer;
super.push(o); // 依賴父類 push 實現細節
}
}
// 危險:若父類 push_many() 內部調用 push(),監控邏輯生效
// 但若父類優化 push_many() 直接操作底層數組繞過 push(),監控失效!
// 子類脆弱地依賴父類「實現細節」而非「契約」
| 模式 | 結構 | 優勢 |
|---|---|---|
| 策略模式 | 接口 + 多實現 + 委託 | 運行時熱插拔算法 |
| 模板方法 | 抽象類定義骨架 + 抽象鉤子 | 復用邏輯流程,延遲具體實現 |
| 裝飾器 | 接口 + 持有同接口引用 | 動態組合行為,避免繼承爆炸 |
| 工廠方法 | 接口 + 子類決定實例化 | 解耦創建邏輯 |
AbstractList)AbstractController、Servlet HttpServletNumber → Integer/Long/Double (最終類)原則:僅在「完全控制父類演進」且「語義真正滿足 is-a」時使用,且父類設計為繼承而生 (@Inheritable 文檔化)。
extends 是雙刃劍,提供代碼復用便利卻以極高耦合為代價。現代 Java 開發應將「接口優先、組合優於繼承」內化為肌肉記憶:
interfaceimplementsextends這樣構建的系統,擁抱變化、經得起重構、支援並行演進。