C 中繼承與組合的區別

2021-10-05 22:20:53 字數 2654 閱讀 2132

c++程式開發中,設計孤立的模擬較容易,設計相互關聯的類卻比較難,這其中會涉及到兩個概念,乙個是繼承(inheritance),乙個是組合(composition)。因為二者有一定的相似性,往往令程式設計師混淆不清。類的組合和繼承一樣,是軟體重用的重要方式。組合和繼承都是有效地利用已有類的資源。但二者的概念和用法不同。

如果類b 有必要使用a 的功能,則要分兩種情況考慮:

若在邏輯上b 是一種a (is a kind of),則允許b 繼承a 的功能,它們之間就是is-a 關係。如男人(man)是人(human)的一種,女人(woman)是人的一種。那麼類man 可以從類human 派生,類woman也可以從類human 派生。示例程式如下:

在uml的術語中,繼承關係被稱為泛化(generalization),類man和woman與類human的uml關係圖可描述如下: 

繼承在邏輯上看起來比較簡單,但在實際應用上可能遭遇意外。比如在oo界中著名的「鴕鳥不是鳥」和「圓不是橢圓」的問題。這樣的問題說明了程式設計和現實世界存在邏輯差異。從生物學的角度,鴕鳥(ostrich)是鳥(bird)的一種,既然是is-a的關係,類costrich應該可以從類cbird派生。但是鴕鳥不會飛,但從cbird那裡繼承了介面函式fly,如下所示:

「圓不是橢圓」同樣存在類似的問題,圓從橢圓類繼承了無用的長短軸資料成員。所以更加嚴格的繼承應該是:若在邏輯上b是a的一種,並且a的所有功能和屬性對b都有意義,則允許b繼承a的所有功能和屬性。

類繼承允許我們根據自己的實現來覆蓋重寫父類的實現細節,父類的實現對於子類是可見的,所以我們一般稱之為白盒復用。繼承易於修改或擴充套件那些被復用的實現,但它這種白盒復用卻容易破壞封裝性。因為這會將父類的實現細節暴露給子類。

若在邏輯上a 是b 的「一部分」(a part of),則不允許b 繼承a 的功能,而是要用a和其它東西組合出b,它們之間就是「has-a關係」。例如眼(eye)、鼻(nose)、口(mouth)、耳(ear)是頭(head)的一部分,所以類head 應該由類eye、nose、mouth、ear 組合而成,不是派生而成。示例程式如下:

如果允許head 從eye、nose、mouth、ear 派生而成,那麼head 將自動具有look、smell、eat、listen 這些功能:

上述程式十分簡短並且執行正確,但是這種設計卻是錯誤的。所以我們要經的起「繼承」的**,避免犯下設計錯誤。

在uml中,上面類的uml關係圖可描述如下: 

實心菱形代表了一種堅固的關係,被包含類的生命週期受包含類控制,被包含類會隨著包含類建立而建立,消亡而消亡。組合屬於黑盒復用,被包含物件的內部細節對外是不可見的,所以它的封裝性相對較好,實現上相互依賴比較小,並且可以通過獲取其它具有相同型別的物件引用或指標,在執行期間動態的定義組合。而缺點就是致使系統中的物件過多。

綜上所述,is-a關係用繼承表示,has-a關係用組合表示,gof在《設計模式》中指出oo設計的一大原則就是:優先使用物件組合,而不是類繼承。

封裝、繼承、多型是物件導向技術的三大機制,封裝是基礎、繼承是關鍵、多型是延伸。繼承是作為關鍵的一部分,如果我們理解不夠深刻,則容易造成程式設計中的不良繼承,影響程式質量。

上文中「圓不是橢圓」這一著名問題,實際上在數學上圓是一種特殊的橢圓,於是會出現下面的繼承:

橢圓存在乙個設定長短軸的成員函式setsize,而圓則不需要。橢圓能做某些圓不能做的事,所以圓繼承自橢圓是不合理的類設計。那麼面對「圓是/不是一種橢圓」這個兩難的問題,我們如何解決。主要有幾下幾種方法: 

(1)使用**技巧來彌補設計缺陷。在子類ccircle中重新定義setsize丟擲異常,或終止程式,或做其他的異常處理,但這些技巧會讓使用者吃驚不已,違背了介面設計的「最小驚訝原則」; 

(2)改變觀點,人為圓是不對稱的。這對於我們思維嚴謹的程式設計師來說,有點不可接受; 

(3)將基類的成員函式setsize刪除。但這回影響橢圓物件的正常使用。 

(4)去掉它們之間的繼承關係。推薦做法,既然圓繼承橢圓是一種不良類設計,我們就應該杜絕。去掉繼承關係,並不代表圓與橢圓就沒有關係,兩個類可以繼承自同乙個類covalshape,不過該類不能執行不對稱的setsize計算,如下圖所示:

其中,橢圓增加了特有的setsize(float x,float y)運算。

不良繼承出現的根本原因在於對繼承的理解不夠深刻,錯把直覺中的「是一種(is-a)」當成了學術中的「子型別(subtype)」概念。在繼承體系中,派生類物件是可以取代基類物件的。而在橢圓和圓的問題上,橢圓類中的成員函式setsize(x,y)違背了這個可置換性,即liskov替換原則。

所有不良繼承都可以歸結為「圓不是橢圓」這一著名具有代表性的問題上。在不良繼承中,基類總會有一些額外能力,而派生類卻無法滿足它。這些額外的能力通常表現為乙個或多個成員函式提供的功能。要解決這一問題,要麼使基類弱化,要麼消除繼承關係,需要根據具體情形來選擇。

C 中繼承與組合的區別

物件和類是 c 中的重要內容,物件 object 是類 class 的乙個例項 instance 物件導向設計的重點是類的設計,而不是物件的設計。對於 c 程式而言,設計孤立的類是比較容易的,難的是正確設計基類及其派生類。這就和 繼承 inheritance 和 組合 composition 有重要...

C 繼承與組合的區別

1 繼承與組合 2 繼承和組合的使用場景 3 繼承和組合的區別 4 繼承和組合的優缺點 1 繼承的優缺點 2 組合的優缺點 c 程式開發中,設計孤立的模擬較容易,設計相互關聯的類卻比較難,這其中會涉及兩個概念,乙個是繼承 inheritance 乙個是組合 composition 因為二者有一定的相...

組合與繼承的區別

組合和繼承都允許在新的類中設定子物件,只是組合是顯式的,繼承是隱式的。繼承關係 is a 關係 組合關係 has a 關係 例如 car表示汽車物件,vehicle表示交通工具物件,tire表示輪胎物件 如何選擇 兩大原則 不要單純地為了實現 的重用而使用繼承,除非兩個類是 is a 的關係,否則不...