Effective Java Item18:用組合取代繼承
整理 Effective Java 書中 Item 18: Favor composition over inheritance 心得筆記
主旨
繼承常被拿來重用程式碼,但其實風險也很高,尤其當你繼承的類別不是為了擴充而設計。這篇重點是:「與其繼承一個現成的類別,不如把它包進一個欄位(組合)來用」,這樣可以避開繼承帶來的封裝破壞與潛在 bug,讓設計更穩健。
點出問題:繼承容易踩雷
先來看一個直覺但容易出錯的範例:
你想要擴充一個 ArrayList
,加上一個功能來統計 add()
被呼叫幾次。很自然地你可能會寫這樣的繼承:
乍看合理,但實際上可能會出問題。
原因是 ArrayList
的 addAll()
裡面會呼叫 add()
。也就是說當你呼叫 addAll(3個元素)
,addCount
不只加了 3,還多加了每個元素呼叫 add()
的次數,結果會是 6,而不是你想要的 3。
這就是繼承破壞封裝的問題 —— 你依賴了一個你不該知道的內部實作細節(addAll()
呼叫 add()
),當這個實作被改變,你的子類也跟著壞掉。
範例重寫:用組合+轉發解決問題
改寫方式:不要繼承 ArrayList
,而是讓你的類別裡面包一個 List,然後手動「轉發」呼叫。
這樣做的好處是:
- 不會因為
List
的實作方式改變而壞掉 - 加什麼功能都自己控制
- 更容易測試、更符合封裝原則
小結
在 Java 中,繼承是一種強耦合設計。當你繼承某個類別,就等於綁住它的行為與未來的變化,這會讓程式容易壞、難測試、難維護。
請記得這句原則:
❗ 如果你只是想要「使用功能」,就用組合;
✅ 只有當你能說出「X is-a Y」,才考慮繼承。
從今天開始,用更穩健的思維寫程式吧。
Read other posts