詳解繼承與組合的優缺點


組合與繼承都是提高代碼可重用性的手段。在設計對象模型時,可以按照語義來識別類之間的組合關係和繼承關係。在有些情況下,採用組合關係或者繼承關係能完成同樣的任務,組合和繼承存在着對應關係:組合中的整體類和繼承中的子類對應,組合中的局部類和繼承中的父類對應,如下圖:


組合:


繼承:



一、基礎知識

我們先用代碼幫大家來理解一下組合和繼承:

1、對於已經存在Parent類時需要擴展其方法時

結構圖:


繼承代碼:

class Parent
    {
        public void Method1() 
        { 
        }
        public void Method2() { }
        public void Method3() { }
    }
 class Child1:Parent
    {
        public void MethodA() { }
    }
 class Child2:Parent
    {
        public void MethodA() { }
    }

組合代碼:

class ComponentA
    {
        Parent p = new Parent();
        public void Method2()
        {
            p.Method2();
        }
        public void MethodA()
        {
        }
    }
 class ComponentB
    {
        Parent p = new Parent();
        public void Method2() {
            p.Method2();
        }
        public void MethodB()
        { 
        }
    }

2、如果發現兩個類具有很多代碼相同的類需要抽象時,如下圖A,B兩個類,這兩個類中method1,和method3兩個方法代碼相同



繼承:


實現代碼:

class A:C
    {
        public void MethodB() { }
        public override void Method2()
        {
            
        }
    }
    class B:C
    {
        public override void Method2()
        {
            
        }
        public void MethodB() { }
    }
    class C
    {
        public void Method1() { }
        public virtual void Method2() { }
        public void Method3() { }
    }


組合:


class A
    {
        public void MethodA() { }
        C c = new C();
        public void Method1()
        {
            c.Method1();
        }
        public void Method2() { }

    }
    class B
    {
        public void MethodB() { }
        public void Method2() { }
    }
    class C
    {
        public void Method1() { }
        public void Method2() { }
        public void Method3() { }
    }

二、繼承與組合的優缺點

合 關 系

繼 承 關 系

優點:不破壞封裝,整體類與局部類之間鬆耦合,彼此相對獨立

缺點:破壞封裝,子類與父類之間緊密耦合,子類依賴於父類的實現,子類缺乏獨立性

優點:具有較好的可擴展性

缺點:支持擴展,但是往往以增加系統結構的複雜度爲代價

優點:支持動態組合。在運行時,整體對象可以選擇不同類型的局部對象

缺點:不支持動態繼承。在運行時,子類無法選擇不同的父類

優點:整體類可以對局部類進行包裝,封裝局部類的接口,提供新的接口

缺點:子類不能改變父類的接口

缺點:整體類不能自動獲得和局部類同樣的接口

優點:子類能自動繼承父類的接口

缺點:創建整體類的對象時,需要創建所有局部類的對象

優點:創建子類的對象時,無須創建父類的對象

1、爲什麼繼承破壞封裝性:

鴨子中不想要“飛”的方法,但因爲繼承無法封裝這個無用的“飛”方法


2、爲什麼繼承緊耦合:

當作爲父類的BaseTable感覺Insert這個名字不合適時,如果希望將其修改成Create方法,那使用了子類對象Insert方法將會編譯出錯,可能你會覺得這改起來還算容易,因爲有重構工具一下子就好了並且編譯錯誤改起來很容易。但如果BaseTable和子類在不同的程序集中,維護的人員不同,BaseTable程序集升級,那本來能用的代碼忽然不能用了,這還是很難讓人接受的

3、爲什麼繼承擴展起來比較複雜

當圖書和數碼的算稅方式和數碼產品一樣時,而消費類產品的算稅方式是另一樣時,如果採用繼承方案可能會演變成如下方式:

這樣如果產品繼續增加,算稅方式繼續增加,那繼承的層次會非常複雜,而且很難控制,而使用組合就能很好的解決這個問題,參見:面向對象思想的頭腦風暴(一)

4、繼承不能支持動態繼承

這個其實很好理解,因爲繼承是編譯期就決定下來的,無法在運行時改變,如3例中,如果用戶需要根據當地的情況選擇計稅方式,使用繼承就解決不了,而使用組合結合反射就能很好的解決。

5、爲什麼繼承 子類不能改變父類接口

如2中的圖

子類中覺得Insert方法不合適,希望使用Create方法,因爲繼承的原因無法改變

 

三、如何使用繼承

 

1、精心設計專門用於被繼承的類,繼承樹的抽象層應該比較穩定,一般不要多於三層。

2、對於不是專門用於被繼承的類,禁止其被繼承。

3、優先考慮用組合關係來提高代碼的可重用性。

4、子類是一種特殊的類型,而不只是父類的一個角色

5、子類擴展,而不是覆蓋或者使父類的功能失效

 

四、組合的缺點:

1、整體類不能自動獲得和局部類同樣的接口

如果父類的方法子類中幾乎都要暴露出去,這時可能會覺得使用組合很不方便,使用繼承似乎更簡單方便。但從另一個角度講,實際上也許子類中並不需要暴露這些方法,客戶端組合應用就可以了。所以上邊推薦不要繼承那些不是爲了繼承而設計的類,一般爲了繼承而設計的類都是抽象類。

2、創建整體類的對象時,需要創建所有局部類的對象

這個可能沒什麼更好的辦法,但在實際應用中並沒有多出多少代碼。

五、相關原則

1、里氏代換原則(LSP)(以下轉自http://www.cnblogs.com/zhenyulu/articles/36061.html

Liskov Substitution Principle(里氏代換原則):子類型(subtype)必須能夠替換它們的基類型。

 

白馬、黑馬


反過來的代換不成立
《墨子·小取》說:"娣,美人也,愛娣,非愛美人也……"娣便是妹妹,哥哥喜愛妹妹,是因爲兩人是兄妹關係,而不是因爲妹妹是個美人。因此,喜愛妹妹不等同於喜愛美人。用面嚮對象語言描述,美人是基類,妹妹是美人的子類。哥哥作爲一個有"喜愛()"方法,接受妹妹作爲參數。那麼,這個"喜愛()"方法一般不能接受美人的實例。


下邊那個長方形正方形的例子我就不轉了,大家可以到上邊那個博客地址中瞭解。

2、合成/聚合複用原則(CARP)(以下轉自http://www.cnblogs.com/zhenyulu/articles/36068.html
合成/聚合複用原則(Composite/Aggregate Reuse Principle或CARP)經常又叫做合成複用原則(Composite Reuse Principle或CRP),就是在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分;新對象通過向這些對象的委派達到複用已有功能的目的。

簡而言之,要儘量使用合成/聚合,儘量不要使用繼承。

o Design to interfaces.
o Favor composition over inheritance.
o Find what varies and encapsulate it.
(摘自:Design Patterns Explained)

區分"Has-A"與"Is-A"

"Is-A"是嚴格的分類學意義上定義,意思是一個類是另一個類的"一種"。而"Has-A"則不同,它表示某一個角色具有某一項責任。

導致錯誤的使用繼承而不是合成/聚合的一個常見的原因是錯誤的把"Has-A"當作"Is-A"。

例如:


實際上,僱員、經理、學生描述的是一種角色,比如一個人是"經理"必然是"僱員",另外一個人可能是"學生僱員",在上面的設計中,一個人無法同時擁有多個角色,是"僱員"就不能再是"學生"了,這顯然是不合理的。

錯誤源於把"角色"的等級結構與"人"的等級結構混淆起來,誤把"Has-A"當作"Is-A"。解決辦法:


總結:

根據我們前面講的內容我們可以發現繼承的缺點遠遠多於優點,儘管繼承在學習OOP的過程中得到了大量的強調,但並不意味着應該儘可能地到處使用它。相反,使用它時要特別慎重。只有在清楚知道繼承在所有方法中最有效的前提下,纔可考慮它。 繼承最大的優點就是擴展簡單,但大多數缺點都很致命,但是因爲這個擴展簡單的優點太明顯了,很多人並不深入思考,所以造成了太多問題,希望這篇文章能引發你一些思考。

 

參考:

http://blog.csdn.net/Cpp_Java_Man/archive/2006/05/02/705279.aspx

http://blog.csdn.net/zjliu1984/archive/2009/06/26/4299657.aspx

http://www.cnblogs.com/zhenyulu/category/6930.html

http://www.cnblogs.com/bluedy1229/archive/2008/11/19/1286692.html




作者:Lance
出處:http://www.cnblogs.com/nuaalfm/



發佈了54 篇原創文章 · 獲贊 11 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章