「若我能重新設計 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; } } |
| 強耦合 | 繼承是「白盒復用」,子類依賴父類實現細節;組合是「黑盒復用」,僅依賴介面契約 | 修改父類私有方法 → 所有子類需重測 |
void process(LinkedListlist) { // 鎖死 LinkedList list.add("item"); for (String s : list) System.out.println(s); }
void process(Collectioncoll) { // 接受 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"));
void process(Iteratorit) { // 僅需遍歷能力 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()); // 相容性場景
鐵律:業務邏輯代碼中,**80% 以上應針對介面編程**,僅在構建物件時 `new 具體實現()` 或由 DI 容器注入。
這樣的代碼:易測試 (Mock 介面)、易重構 (替換實現零修改調用端)、易併行開發 (介面穩定、實現並行開發)、易演進 (新增實現不破壞舊代碼)。