在《Java 設計模式》一書中詳細闡述:優秀的設計者會像躲避瘟疫一樣避免使用實現繼承(extends 關係)。實際上,80% 的代碼應完全使用 interface 編寫,而非透過 extends。James Gosling(Java 之父)曾在公開場合表示:「如果重構 Java,我會拋棄 classes」,真正的問題不在 class 本身,而在於實現繼承;接口繼承(implements)才是更好的選擇。
明確使用具體類名會將代碼鎖定在特定實現上,增加底層變更的困難。在敏捷開發強調「並行設計與開發」的當下,靈活性是核心。看看這個例子:
// 緊耦合寫法
void f() {
LinkedList list = new LinkedList();
g(list);
}
void g(LinkedList list) {
list.add(...);
g2(list);
}
若需求變更需將 LinkedList 換成 HashSet,不僅 f() 要改,所有傳遞該列表的方法(g()、g2()…)全得跟著修改。
改用介面編程:
// 鬆耦合寫法
void f() {
Collection list = new LinkedList(); // 只此一處 new 具體類
g(list);
}
void g(Collection list) {
list.add(...);
g2(list);
}
現在替換實現只需改一行 new HashSet(),其餘代碼完全不用動。更進一步,參數類型可再收窄為 Iterator,甚至接受自定義產生資料的迭代器,極大提升擴展性。
實現繼承會製造「脆弱的基類問題」。派生類與基類緊密耦合,基類看似安全的修改可能導致所有派生類失效。經典案例:自訂 Stack extends ArrayList 並維護 stack_pointer,但使用者呼叫繼承來的 clear() 會導致指標與實際資料不同步,造成未定義行為。解決方案是覆寫所有可能修改狀態的方法——但這等於把基類當介面用,還不如直接用組合(private ArrayList the_data)封裝。
將資料結構封裝而非繼承,能徹底避開脆弱基類問題。同樣的 Stack 用組合實現:
class Stack {
private int stack_pointer = 0;
private ArrayList the_data = new ArrayList();
public void push(Object article) { the_data.add(stack_pointer++, article); }
public Object pop() { return the_data.remove(--stack_pointer); }
// 不暴露 ArrayList 的任何方法,介面乾淨可控
}
即使後續需監控堆疊最大深度(MonitorableStack),也只需委派呼叫,不受基類實現細節干擾。
private,避免 getter/setter 洩露內部結構。遵循這些原則,不僅代碼更易維護、測試,並行開發效率也會顯著提升。正如作者所言:「我沒有時間重寫代碼,所以我遵循規則」——嚴守 OO 原則,本質上是為了開發速度與品質的雙贏。