極限編程》(Extreme programming)的指導原則之一是「只要能用,就作最簡單的」。一個彷佛須要繼承的設計經常可以戲劇性地使用組合來代替而大簡化,從而使其更加靈活。所以,在考慮一個設計時,問問本身:「使用組合是否是更簡單?這裏真的須要繼承嗎?它能帶來什麼好處?」編程
繼承和組合的比較:設計
面向對象系統中功能複用的兩種最經常使用技術是類繼承和對象組合(object composition)。正如咱們已解釋過的,類繼承容許你根據其餘類的實現來定義一個類的實現。這種經過生成子類的複用一般被稱爲白箱複用(white-box reuse)。術語「白箱」是相對可視性而言:在繼承方式中,父類的內部細節對子類可見。對象
對象組合是類繼承以外的另外一種複用選擇。新的更復雜的功能能夠經過組裝或組合對象來得到。對象組合要求被組合的對象具備良好定義的接口。這種複用風格被稱爲黑箱複用(black-box reuse),由於對象的內部細節是不可見的。對象只以「黑箱」的形式出現。繼承
繼承和組合各有優缺點。類繼承是在編譯時刻靜態定義的,且可直接使用,由於程序設計語言直接支持類繼承。類繼承能夠較方便地改變被複用的實現。當一個子類重定義一些而不是所有操做時,它也能影響它所繼承的操做,只要在這些操做中調用了被重定義的操做。接口
可是類繼承也有一些不足之處。首先,由於繼承在編譯時刻就定義了,因此沒法在運行時刻改變從父類繼承的實現。更糟的是,父類一般至少定義了部分子類的具體表示。由於繼承對子類揭示了其父類的實現細節,因此繼承常被認爲「破壞了封裝性」 。子類中的實現與它的父類有如此緊密的依賴關係,以致於父類實現中的任何變化必然會致使子類發生變化。當你須要複用子類時,實現上的依賴性就會產生一些問題。若是繼承下來的實現不適合解決新的問題,則父類必須重寫或被其餘更適合的類替換。這種依賴關係限制了靈活性並最終限制了複用性。一個可用的解決方法就是隻繼承抽象類,由於抽象類一般提供較少的實現。rem
對象組合是經過得到對其餘對象的引用而在運行時刻動態定義的。組合要求對象遵照彼此的接口約定,進而要求更仔細地定義接口,而這些接口並不妨礙你將一個對象和其餘對象一塊兒使用。這還會產生良好的結果:由於對象只能經過接口訪問,因此咱們並不破壞封裝性;只要類型一致,運行時刻還能夠用一個對象來替代另外一個對象;更進一步,由於對象的實現是基於接口寫的,因此實現上存在較少的依賴關係。it
對象組合對系統設計還有另外一個做用,即優先使用對象組合有助於你保持每一個類被封裝,並被集中在單個任務上。這樣類和類繼承層次會保持較小規模,而且不太可能增加爲不可控制的龐然大物。另外一方面,基於對象組合的設計會有更多的對象 (而有較少的類),且系統的行爲將依賴於對象間的關係而不是被定義在某個類中。io
這導出了咱們的面向對象設計的第二個原則:優先使用對象組合,而不是類繼承。編譯