爲何更喜歡使用組合而不是繼承? 每種方法都有哪些取捨? 何時應該選擇繼承而不是合成? java
您想強迫本身(或另外一位程序員)堅持什麼,以及什麼時候容許本身(或另外一位程序員)擁有更多自由。 有人爭辯說,當您想強迫某人處理/解決特定問題,以避免他們走錯方向時,繼承頗有用。 程序員
Is-a
和Has-a
是有用的經驗法則。 數據庫
個人通常經驗法則: 在使用繼承以前,請考慮組合是否更有意義。 安全
緣由: 子類化一般意味着更多的複雜性和聯繫性,即更容易更改,維護和擴展而不犯錯誤。 框架
Sun的蒂姆·佈德羅 ( Tim Boudreau)給出了更加完整和具體的答案 : jsp
在我看來,使用繼承的常見問題是: 模塊化
- 無辜的行爲會產生意想不到的結果 -經典示例是在初始化子類實例字段以前,從超類構造函數調用可重寫方法。 在理想世界中,沒有人會這樣作。 這不是一個完美的世界。
- 它爲子類提供了不正確的誘惑,使他們沒法對方法調用的順序進行假設,而且這種假設-若是超類可能隨時間演變,則這種假設每每不穩定。 另請參閱個人烤麪包機和咖啡壺類比 。
- 類變得更重 -您不必定知道您的超類在其構造函數中正在執行什麼工做,或者將使用多少內存。 所以,構造一些無辜的輕量級對象可能比您想象的要昂貴得多,並且若是超類不斷髮展,這種狀況可能會隨着時間而改變。
- 它鼓勵子類的爆炸式增加 。 類加載會花費時間,更多類會消耗內存。 在處理NetBeans規模的應用程序以前,這可能不是問題,可是在那兒,咱們遇到了一些實際問題,例如菜單速度慢,由於菜單的首次顯示會觸發大量的類加載。 咱們經過使用更具聲明性的語法和其餘技術來解決此問題,但這也須要花費時間來解決。
- 這使得之後更改內容變得更加困難 -若是您已公開一個類,則交換超類將破壞子類-這是一個選擇,一旦您將代碼公開,便成爲您的選擇。 所以,若是您不更改超類的實際功能,則能夠在之後使用時有更大的自由來進行更改,而沒必要擴展所需的內容。 以子類化JPanel爲例-這一般是錯誤的。 若是子類在某個地方是公共的,則您將永遠沒有機會從新審視該決定。 若是以JComponent getThePanel()的形式訪問它,則仍然能夠執行此操做(提示:以您的API公開內部組件的模型)。
- 對象層次結構沒法擴展(或使它們在之後擴展比預先計劃要困可貴多) -這是經典的「層數過多」問題。 我將在下面進行介紹,以及AskTheOracle模式如何解決它(儘管它可能會冒犯OOP純粹主義者)。
... 函數
若是容許繼承,那麼個人見解是: ui
- 除常量外,不公開任何字段
- 方法應該是抽象的或最終的
- 從超類構造函數不調用任何方法
... spa
全部這些對小型項目的影響要小於大型項目,對私人階級的影響要小於公共項目。
正如許多人所說,我首先要進行檢查-是否存在「是」關係。 若是存在,我一般會檢查如下內容:
基類是否能夠實例化。 也就是說,基類是否能夠是非抽象的。 若是不是抽象的話,我一般更喜歡構圖
例如1.會計師是一名僱員。 可是我不會使用繼承,由於能夠實例化Employee對象。
例如2.書是 SellingItem。 SellingItem沒法實例化-這是抽象概念。 所以,我將使用Inheritacne。 SellingItem是一個抽象基類(或 C#中的接口 )
您如何看待這種方法?
此外,我支持@anon回答「 爲何要徹底使用繼承?」中的內容。
使用繼承的主要緣由不是做爲一種組合形式,而是能夠獲取多態行爲。 若是不須要多態性,則可能不該該使用繼承。
@MatthieuM。 在https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448中說
繼承的問題在於它能夠用於兩個正交的目的:
接口(用於多態)
實現(用於代碼重用)
參考
我我的學會了始終偏向於繼承而不是繼承。 沒有能夠經過繼承解決的程序化問題,而不能經過組合解決。 儘管在某些狀況下可能必須使用Interfaces(Java)或Protocols(Obj-C)。 因爲C ++一無所知,所以您必須使用抽象基類,這意味着您沒法徹底擺脫C ++中的繼承。
組合一般更合乎邏輯,它提供更好的抽象,更好的封裝,更好的代碼重用(尤爲是在很是大的項目中),而且僅由於您在代碼中的任何地方進行了孤立的更改,就不太可能在遠處破壞任何內容。 這也使堅持「 單一責任原則 」變得更加容易,該原則一般被歸納爲「 一個類別發生變化的理由永遠不僅一個。 」,這意味着每一個類別都有一個特定的目的,所以應該僅具備與其用途直接相關的方法。 另外,因爲繼承樹很淺,即便您的項目開始變得很大,也更容易保留概述。 許多人認爲繼承很好地表明瞭咱們的現實世界 ,但這不是事實。 現實世界中使用的構成多於繼承。 幾乎能夠握在手中的每一個現實對象都是由其餘較小的現實對象組成的。
可是,在組合方面也有缺點。 若是您徹底跳過繼承而只關注合成,您會注意到,您常常不得不編寫一些額外的代碼行,若是您使用了繼承,則這些代碼行是沒必要要的。 有時您也被迫重複本身,這違反了DRY原則 (DRY =不重複本身)。 一樣,組合一般須要委託,而一個方法只是調用另外一個對象的另外一個方法,而此調用周圍沒有其餘代碼。 這種「雙重方法調用」(可能會很容易擴展到三重或四重方法調用,甚至更遠)比繼承(繼承人)要差得多,在繼承中,您僅繼承父方法。 調用繼承的方法可能與調用非繼承的方法同樣快,或者可能稍慢一些,但一般仍比兩個連續的方法調用快。
您可能已經注意到,大多數OO語言不容許多重繼承。 雖然在不少狀況下多重繼承能夠真正爲您帶來收益,可是這些都是例外,而不是規則。 每當您遇到「多重繼承將是解決此問題的一個很是酷的功能」的狀況時,一般您都應該從新考慮繼承,由於即便這樣,它也可能須要幾個額外的代碼行,基於組合的解決方案一般會變得更優雅,更靈活且更適合將來。
繼承確實是一個很酷的功能,可是恐怕最近幾年它已經被濫用了。 人們將繼承視爲能夠釘牢這一切的錘子,不管它其實是釘子,螺釘仍是徹底不一樣的東西。
這兩種方式能夠很好地生活在一塊兒,而且實際上彼此支持。
合成只是模塊化地發揮做用:您建立相似於父類的接口,建立新對象並委託對其進行調用。 若是這些對象不須要彼此瞭解,那麼它很是安全且易於使用。 這裏有不少可能性。
可是,若是父類出於某種緣由須要爲經驗不足的程序員訪問「子類」提供的功能,那麼它彷佛是使用繼承的好地方。 父類能夠只調用它本身的抽象「 foo()」,該抽象被子類覆蓋,而後能夠將值提供給抽象基。
看起來不錯,可是在不少狀況下,最好給該類一個實現foo()的對象(甚至手動設置foo()所提供的值),而不是從某個須要繼承的基類繼承新類。要指定的函數foo()。
爲何?
由於繼承是傳遞信息的不良方法 。
這種組合在這裏具備真正的優點:這種關係能夠顛倒:「父類」或「抽象工做者」能夠聚合實現特定接口的任何特定「子」對象+ 能夠在任何其餘類型的父內部設置任何子,它是type 。 而且能夠有任意數量的對象,例如MergeSort或QuickSort能夠對實現抽象Compare接口的對象列表進行排序。 換句話說,實現「 foo()」的任何對象組和能夠利用具備「 foo()」的對象的其餘對象組均可以一塊兒玩。
我能夠想到使用繼承的三個真正緣由:
若是這些是正確的,則可能有必要使用繼承。
使用緣由1並無什麼很差,在對象上具備可靠的接口是很是好的事情。 若是此接口簡單且不會更改,則可使用組合或繼承來完成此操做。 一般,繼承在這裏很是有效。
若是緣由是2,那就有點棘手了。 您真的只須要使用相同的基類嗎? 一般,僅使用相同的基類是不夠的,但這多是您的框架的要求,這是沒法避免的設計考慮。
可是,若是要使用私有變量(狀況3),則可能會遇到麻煩。 若是您認爲全局變量不安全,則應考慮使用繼承來訪問也不安全的私有變量 。 提醒您,全局變量並不全是壞的-數據庫本質上是一大組全局變量。 可是,若是能夠處理,那就很好了。