|
繼承已經(jīng)是一個(gè)古老的話題了,不過最近又在一些地方看到有人討論它,加上自己也有一些想法,因此形成了這篇文章。
繼承好不好?
經(jīng)典的OO理論說:繼承是面向?qū)ο蟮娜蠡弧?br />現(xiàn)代的OO理論說:組合優(yōu)于繼承。
這兩種說法顯然是彼此沖突的。如果組合優(yōu)于繼承的話,那么為什么組合沒有取代繼承成為OO的基石呢?哪一種說法更有道理?
對(duì)這個(gè)問題,簡單的說哪個(gè)比哪個(gè)更好其實(shí)是沒有多大意義的。我們應(yīng)當(dāng)從技術(shù)發(fā)展的歷史角度去看,這兩種說法各自是在什么時(shí)期產(chǎn)生的,它們形成的背景是什么,才能對(duì)此問題有一個(gè)更加深刻的理解。
面向?qū)ο蟮乃枷胄纬膳c上個(gè)世紀(jì)70年代,但真正在軟件開發(fā)陣營中流行開則是在80年代末和90年代初的時(shí)間。巧合的是,這一時(shí)間也正是以Windows 3.x為代表的圖形操作系統(tǒng)興起的時(shí)代。于是面向?qū)ο螽?dāng)時(shí)所面臨的主要問題就是:如何以O(shè)O的理論封裝圖形界面的開發(fā)?很多重要的早期OO思想都是在這個(gè)時(shí)期形成的,包括對(duì)于繼承的使用。
讓我們考慮一下圖形界面的特點(diǎn)。很容易發(fā)現(xiàn):這個(gè)領(lǐng)域確實(shí)非常適合使用繼承,因?yàn)閳D形對(duì)象天生就存在著is-a關(guān)系。比如,所有圖像對(duì)象都是Window,所有對(duì)話框都是Dialog,所有按鈕都是Button,等等。所以我們可以看到的結(jié)果就是:所有的圖形界面框架都大量使用了繼承,而且繼承的層次通常都非常深。例如,下圖是WPF中最主要的界面類——Window的繼承關(guān)系,它的繼承層次深達(dá)9層!
所有圖形框架在繼承方面幾乎無一例外。Java Swing對(duì)圖形框架由于較多使用MVC,因此繼承的深度要淺一些,但是主要的JFrame類繼承深度也達(dá)到了6層:
至此我們應(yīng)該理解,為什么早期OO理論要將繼承作為面向?qū)ο蟮幕?。因?yàn)楫?dāng)時(shí)軟件開發(fā)的領(lǐng)域還比較狹窄,所以很多開發(fā)者根據(jù)自己在圖形領(lǐng)域的開發(fā)經(jīng)驗(yàn)認(rèn)定:繼承是OO必不可少的重要基礎(chǔ),并且應(yīng)當(dāng)盡可能的使用。
隨著歷史的發(fā)展,軟件開發(fā)逐漸進(jìn)入了兩層和三層時(shí)代。程序員發(fā)現(xiàn),原來在桌面應(yīng)用中得心應(yīng)手的繼承突然之間不那么好用了。為什么呢?
原因之一:兩層和三層開發(fā)的主要工作之一是對(duì)實(shí)體建模。而現(xiàn)實(shí)中的實(shí)體大多數(shù)是相對(duì)獨(dú)立的,它們之間的關(guān)系更多的表現(xiàn)為實(shí)體之間的關(guān)聯(lián),而不是從屬關(guān)系;
原因之二,很重要的現(xiàn)實(shí)問題:多層開發(fā)的主要物質(zhì)基礎(chǔ)之一——關(guān)系數(shù)據(jù)庫,無法很自然的描述繼承關(guān)系。事實(shí)上這也是ORM出現(xiàn)的重要理由之一。但即使是現(xiàn)在最好的ORM工具,要在數(shù)據(jù)庫中描述繼承關(guān)系仍然非常復(fù)雜。這迫使程序員在相當(dāng)程度上放棄了繼承;
原因之三:分層的開發(fā)方式逐漸流行開來,而繼承造成的類屬關(guān)系耦合非常不利于分層。
出于這些考慮,現(xiàn)代的OO理論為什么更加推薦組合而非繼承,應(yīng)該就容易理解了。
那么現(xiàn)代OO理論是不是對(duì)于繼承的看法就完美了呢?我認(rèn)為也不是。事實(shí)上我認(rèn)為,現(xiàn)代OO理論存在著忽視繼承的問題,很多理論書籍只是簡單的告訴我們優(yōu)先使用組合,而根本就不告訴我們?cè)谑裁磿r(shí)候應(yīng)當(dāng)合理使用繼承,什么時(shí)候不應(yīng)當(dāng)使用。這是從早期OO的過度使用繼承跳到了另一個(gè)極端,也是不可取的。
接下類我要講講對(duì)于繼承的幾個(gè)常見的錯(cuò)誤觀念。
1. “組合優(yōu)于繼承。”
就一般的意義上說,這個(gè)講法是沒錯(cuò)的,但問題在于實(shí)在太簡略了。它并沒有告訴我們什么情況下組合優(yōu)于繼承。一個(gè)很自然的問題就是,如果組合在任何情況下都優(yōu)于繼承的話,那繼承還有存在的必要嗎?
有些情況下繼承確實(shí)比組合要好。再回到圖形界面的例子,Button繼承于Window(這是早期MFC的叫法;在WinForm/WPF的分類中,Button繼承于Control,Window通常用來定義頂層窗口),這是沒有問題的,如果一定要用組合來實(shí)現(xiàn)Button的話,反而會(huì)導(dǎo)致不必要的復(fù)雜性。之所以這種情況下繼承更好,根本原因是這里存在著確定的is-a關(guān)系(Button is a Window)。所以我們可以得出這樣一個(gè)結(jié)論:如果語義上存在著明確的is-a關(guān)系,則考慮使用繼承;如果沒有,使用組合。
需要說明的是,這個(gè)結(jié)論其實(shí)也并不是完整的,原因我在后面還會(huì)繼續(xù)講到。
2. “繼承的目的是為了復(fù)用。”
這個(gè)說法根本是錯(cuò)誤的,但就是這個(gè)錯(cuò)誤說法的流行程度簡直讓人吃驚。繼承并不是為了復(fù)用,繼承的根本目的是為了對(duì)現(xiàn)實(shí)世界進(jìn)行更好的建模,容易復(fù)用只是優(yōu)秀模型的一個(gè)必然結(jié)果而已。我們不能倒果為因,特別是,我們不應(yīng)該為了復(fù)用的目的而去繼承。
舉一個(gè)現(xiàn)實(shí)的例子。汽車可以復(fù)用輪子的一些特性(比如可以Run和Stop),那么我們應(yīng)當(dāng)讓汽車從輪子繼承嗎?我看到真的有一些人就是這么建模的。但是從邏輯上想一想就知道,這是非常不合理的,汽車并不是輪子。我們建立了一個(gè)錯(cuò)誤的模型,這會(huì)讓我們?cè)谝院蟾冻龃鷥r(jià)——比如說,要讓汽車能夠換輪子怎么辦?只好傻眼了。
再次強(qiáng)調(diào):繼承的目的不是復(fù)用,不應(yīng)當(dāng)為了能夠復(fù)用而使用繼承。你應(yīng)當(dāng)盡力去建立一個(gè)邏輯合理的模型,不應(yīng)該僅僅為了方便而扭曲這個(gè)模型。
3. 只要存在is-a關(guān)系就應(yīng)當(dāng)使用繼承
在第一點(diǎn)我說過:如果語義上存在著明確的is-a關(guān)系,則考慮使用繼承;如果沒有,使用組合。我還補(bǔ)充說這個(gè)結(jié)論并不完整,這里就會(huì)說明原因。
我們還是從一個(gè)例子說起。下面是許多OO書籍都會(huì)提到的一個(gè)經(jīng)典例子:
在這個(gè)模型中,Sales和Manager都是Employee,但是它們計(jì)算薪水的方法是不同的。不同的記薪方法可以通過重載getSalary()方法來實(shí)現(xiàn)。
這么經(jīng)典的例子有沒有問題呢?有!我們可以這樣想,“如果雇員被提升為經(jīng)理,會(huì)怎么樣?”
問題來了。在OO的世界中,對(duì)象所屬的類型是這個(gè)對(duì)象的本質(zhì)屬性,任何對(duì)象在生命期間無法改變自己所屬的類別。但是現(xiàn)實(shí)中對(duì)象的身份很多時(shí)候是可以改變的。我們從這里可以發(fā)現(xiàn)繼承的一個(gè)重大問題:一旦對(duì)象的身份發(fā)生改變,那么繼承層次就完全崩潰了。
那么圖形界面中為什么可以使用繼承呢?因?yàn)閳D形界面領(lǐng)域的對(duì)象身份是相當(dāng)穩(wěn)定的。Button就是Button,它不會(huì)突然變成一個(gè)頂層窗口。所以這里使用繼承不會(huì)發(fā)生任何問題。但是對(duì)于類型可變的場合,繼承是不適合的。
從建模的角度,我們也可以這樣理解:是Sales還是Manager,并不是一個(gè)人的本質(zhì)屬性,它是可變的。一個(gè)人的本質(zhì)屬性只有他自身(姓名、性別事實(shí)上都是可變的)。我們不能夠把非本質(zhì)屬性應(yīng)用到繼承層次上面。
所以上面的結(jié)論應(yīng)該這樣表述才算完整:如果語義上存在著明確的is-a關(guān)系,并且這種關(guān)系是穩(wěn)定的、不變的,則考慮使用繼承;如果沒有is-a關(guān)系,或者這種關(guān)系是可變的,使用組合。
我們可以使用策略模式來將上面的例子重構(gòu)為使用組合,如下圖所示:
從上述結(jié)論我們可以看到,繼承的使用的確是受到很多限制,在很多情況下也確實(shí)是組合優(yōu)于繼承。但是不分場合、不論條件的認(rèn)為組合一定比繼承好,也是過于教條主義的表現(xiàn)。合理的做法只有一個(gè):具體問題具體分析。
NET技術(shù):閑說繼承,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。