首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

為什麼 Java 繼承 (extends) 多數有害:介面導向設計的核心論據與實踐

     編輯:Java 程式設計入門

核心觀點

「若我能重新設計 Java,我會拋棄 `class` 繼承。」—— James Gosling (Java 之父)

實作繼承 (`extends`) 引入強耦合、破壞封裝、限制靈活性;介面繼承 (`implements`) 配合組合/委派,才是構建可維護、可擴展系統的正道。

一、繼承的三大原罪

問題說明代碼反例
失去靈活性具體類名硬編碼於變數宣告、參數、返回型別,替換實現需修改所有依賴處LinkedList list = new LinkedList(); g(list); // g(LinkedList) 無法傳入 HashSet
破壞封裝子類可直接存取父類 `protected` 欄位/方法,父類內部變更波及所有子類class Base { protected int x; } class Sub extends Base { void foo() { x = 1; } }
強耦合繼承是「白盒復用」,子類依賴父類實現細節;組合是「黑盒復用」,僅依賴介面契約修改父類私有方法 → 所有子類需重測

二、介面導向設計:以 Collection 框架為例

反面教材:具體類型滲透

void process(LinkedList list) { // 鎖死 LinkedList
    list.add("item");
    for (String s : list) System.out.println(s);
}

正面範例:介面型別參數

void process(Collection coll) { // 接受 List/Set/Queue 任意實現
    coll.add("item");
    for (String s : coll) System.out.println(s);
}

// 呼叫端
process(new ArrayList<>());   // 可隨時換成
process(new HashSet<>());
process(new LinkedList<>());
process(Arrays.asList("a","b"));

更進一步:接受 Iterator

void process(Iterator it) { // 僅需遍歷能力
    while (it.hasNext()) System.out.println(it.next());
}

// 呼叫端極致靈活
process(list.iterator());
process(set.iterator());
process(map.keySet().iterator());
process(Stream.of("a","b").iterator());
process(new FileReader("data.txt").lines().iterator()); // 任何資料源

三、組合優於繼承:策略模式實戰

// 介面定義行為契約
interface CompressionStrategy {
    byte[] compress(byte[] data);
}

// 具體策略
class GzipStrategy implements CompressionStrategy { ... }
class Lz4Strategy implements CompressionStrategy { ... }

// 使用組合的上下文
class Compressor {
    private final CompressionStrategy strategy; // 組合,非繼承

    public Compressor(CompressionStrategy strategy) {
        this.strategy = strategy;
    }

    public byte[] compress(byte[] data) {
        return strategy.compress(data);
    }
}

// 客戶端動態切換
Compressor c = new Compressor(new Lz4Strategy()); // 高效能場景
Compressor c2 = new Compressor(new GzipStrategy()); // 相容性場景

四、何時「可以」使用繼承

  1. 模板方法模式:抽象類定義演算法骨架,子類實現特定步驟 (如 `AbstractList`)。
  2. 框架擴展點:Servlet `HttpServlet`、Spring `AbstractController`、JUnit `TestCase`——框架設計者提供骨架,使用者填空。
  3. 真正的「是一種」關係且行為不可變:`Final` 類別、值物件層級 (極少見)。

鐵律:業務邏輯代碼中,**80% 以上應針對介面編程**,僅在構建物件時 `new 具體實現()` 或由 DI 容器注入。

五、現代 Java 的介面增強 (Java 8+)

  • Default 方法:介面提供預設實現,減少抽象類需求。
  • Static 方法:工具方法直接定義在介面 (如 `Stream.of()`)。
  • Functional Interface:單一抽象方法 (SAM) 自動轉換為 Lambda,極大簡化策略/回調寫法。

六、總結:從「繼承思維」轉向「契約思維」

  1. 定義 **介面** 描述「能做什麼」(What)。
  2. 提供 **多種實現** 滿足「怎麼做」(How)。
  3. 使用 **組合/依賴注入** 組裝對象圖。
  4. 保持類 `final`、欄位 `private`、方法 `final` (除非刻意開放擴展)。

這樣的代碼:易測試 (Mock 介面)、易重構 (替換實現零修改調用端)、易併行開發 (介面穩定、實現並行開發)、易演進 (新增實現不破壞舊代碼)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved