典範轉移
為何 Rust 與 Go 選擇組合而非繼承?
繼承的遺產:對傳統物件導向的批判性重估
繼承(Inheritance)曾是物件導向的基石,承諾程式碼重用與多型。然而,隨著系統日趨複雜,其內在缺陷逐漸暴露,促使現代語言如 Rust 和 Go 尋求更好的替代方案。本節將探討繼承帶來的幾個核心問題。
脆弱基底類別問題 (The Fragile Base Class Problem) ➤
對基底類別看似無害的修改,卻可能意外破壞所有衍生類別的運作。這是因為繼承破壞了「封裝」,子類別常不自覺地依賴父類別的內部實作細節。當父類別實作變更時,即使公開介面不變,也可能導致子類別行為錯誤。
菱形問題 (The Diamond Problem) ➤
當一個類別 D 同時繼承自 B 和 C,而 B 和 C 又都繼承自 A 時,若 B 和 C 都覆寫了 A 的某個方法,D 在呼叫該方法時就會產生歧義。這暴露了單一、僵化的「是一種」(Is-A) 階層,不足以描述一個物件「擁有多種能力」的場景。
A
/
\
B
C
\
/
D
緊密耦合與階層僵化 ➤
繼承在父子類別間建立了程式碼中最緊密的耦合關係,使得系統非常僵硬,難以適應需求變更。一旦繼承體系建立,重構的成本會變得極其高昂,限制了程式的靈活性。
組合的興起:設計原則的演進
面對繼承的挑戰,軟體設計領域轉向了更靈活的「組合」(Composition)。其核心原則「多用組合,少用繼承」建議開發者優先透過組合來擴展功能,將繼承保留給真正需要多型的場景。組合模型化的是一種「有一個」(Has-A) 的關係。
✔️ 彈性 (Flexibility)
物件間鬆散耦合,允許在執行期間動態改變行為,避免「子類別爆炸」。
✔️ 封裝 (Encapsulation)
尊重並保護了封裝,透過「黑箱」重用,容器物件只依賴元件的公開介面。
✔️ 可測試性 (Testability)
依賴的元件明確可替換,易於隔離和模擬,顯著簡化了單元測試。
Go vs. Rust:深度比較
Go 和 Rust 都摒棄了繼承,但它們基於組合的替代方案在哲學和機制上各有千秋。Go 追求簡潔與開發效率,而 Rust 將正確性與效能控制置於首位。點擊下方按鈕,探索它們的具體實現差異。