官术网_书友最值得收藏!

1.2 什么是繼承

本節(jié)將介紹以下內(nèi)容:

·什么是繼承?

·繼承的實(shí)現(xiàn)本質(zhì)

·繼承的分類(lèi)與規(guī)則

·繼承與聚合

·繼承的局限

1.2.1 引言

繼承,一個(gè)熟悉而容易產(chǎn)生誤解的話(huà)題。這是大部分人對(duì)繼承最直觀的感受。說(shuō)它熟悉,是因?yàn)樽鳛槊嫦驅(qū)ο蟮娜笠刂坏睦^承,每個(gè)技術(shù)研究者都會(huì)在職業(yè)生涯中不斷地重復(fù)關(guān)于繼承的話(huà)題;說(shuō)它容易產(chǎn)生誤解,是因?yàn)樗偸呛头庋b、多態(tài)交織在一起,形成復(fù)雜的局面。以繼承為例,如何理清多層繼承的機(jī)制,如何了解實(shí)現(xiàn)繼承與接口繼承的異同,如何體會(huì)繼承與多態(tài)的關(guān)系,似乎都不是件簡(jiǎn)單的事情。

本節(jié)希望將繼承中最為頭疼,最為復(fù)雜的問(wèn)題統(tǒng)統(tǒng)拿出來(lái)曬一曬,以防時(shí)間久了,不知不覺(jué)在使用者那里發(fā)霉生蟲(chóng)。

本節(jié)不會(huì)花太多筆墨做系統(tǒng)性的論述,如有需要請(qǐng)參考其他技術(shù)專(zhuān)著上更詳細(xì)的分析。我們將從關(guān)于繼承的熱點(diǎn)出發(fā),逐個(gè)擊破,最后總結(jié)規(guī)律,期望用這種方式實(shí)現(xiàn)對(duì)繼承全面的了解,讓你掌握什么才是繼承。

1.2.2 基礎(chǔ)為上

正如引言所述,繼承是個(gè)容易產(chǎn)生誤解的技術(shù)話(huà)題。那么,對(duì)于繼承,就應(yīng)該著手從這些容易誤解與引起爭(zhēng)論的話(huà)題來(lái)尋找關(guān)于全面認(rèn)識(shí)和了解繼承的答案。一點(diǎn)一滴擺出來(lái),最后再對(duì)分析的要點(diǎn)做歸納,形成一種系統(tǒng)化認(rèn)識(shí)。這是一種探索問(wèn)題的方式,用于剖析繼承這一話(huà)題真是再恰當(dāng)不過(guò)了。

不過(guò),解密之前,我們還是按照技術(shù)分析的慣例,從基本出發(fā),以簡(jiǎn)潔的方式來(lái)快速了解關(guān)于繼承最基本的概念。首先,認(rèn)識(shí)一張比較簡(jiǎn)單的動(dòng)物分類(lèi)圖(圖1-1),以便引入我們對(duì)繼承概念的介紹。

圖1-1 繼承關(guān)系圖

從圖1-1中,我們可以獲得的信息包括:

·動(dòng)物繼承關(guān)系是以一定的分類(lèi)規(guī)則進(jìn)行的,將相同屬性和特征的動(dòng)物及其類(lèi)別抽象為一類(lèi),類(lèi)別與類(lèi)別之間的關(guān)系反映為對(duì)相似或者對(duì)不相似的某種抽象關(guān)系,例如鳥(niǎo)類(lèi)一般都能飛,而魚(yú)類(lèi)一般都生活在水中。

·位于繼承圖下層的類(lèi)別繼承了上層所有類(lèi)別的特性,形成一種IS-A的關(guān)系,例如我們可以說(shuō),人類(lèi)IS-A哺乳類(lèi)、人類(lèi)IS-A脊椎類(lèi)。但是這種關(guān)系是單向的,所以我們不能說(shuō)鳥(niǎo)類(lèi)IS-A雞。

·動(dòng)物繼承圖自上而下是一種逐層具體化過(guò)程,而自下而上是一種逐層抽象化過(guò)程,這種抽象化關(guān)系反映為上下層之間的繼承關(guān)系。例如,最高層的動(dòng)物具有最普遍的特征,而最低層的人則具有較具體的特征。

·下層類(lèi)型只能從上層類(lèi)型中的某一個(gè)類(lèi)別繼承,例如鯨類(lèi)的上層只能是哺乳類(lèi)一種,因此是一種單繼承形式。

·這種繼承關(guān)系中,層與層的特性是向下傳遞的,例如鳥(niǎo)類(lèi)具有脊椎類(lèi)的特征,鶴類(lèi)也具有脊椎類(lèi)的特征,而所有的類(lèi)都具有動(dòng)物的特征,因此說(shuō)動(dòng)物是這個(gè)層次關(guān)系的根。

我們將這種現(xiàn)實(shí)世界的對(duì)象抽象化,就形成了面向?qū)ο笫澜绲睦^承機(jī)制。因此,關(guān)于繼承,我們可以定義為:

繼承,就是面向?qū)ο笾蓄?lèi)與類(lèi)之間的一種關(guān)系。繼承的類(lèi)稱(chēng)為子類(lèi)、派生類(lèi),而被繼承類(lèi)稱(chēng)為父類(lèi)、基類(lèi)或超類(lèi)。通過(guò)繼承,使得子類(lèi)具有父類(lèi)的屬性和方法,同時(shí)子類(lèi)也可以通過(guò)加入新的屬性和方法或者修改父類(lèi)的屬性和方法建立新的類(lèi)層次。

繼承機(jī)制體現(xiàn)了面向?qū)ο蠹夹g(shù)中的復(fù)用性、擴(kuò)展性和安全性。為面向?qū)ο筌浖_(kāi)發(fā)與模塊化軟件架構(gòu)提供了最基本的技術(shù)基礎(chǔ)。

在.NET中,繼承按照其實(shí)現(xiàn)方式的不同,一般分類(lèi)如下。

·實(shí)現(xiàn)繼承:派生類(lèi)繼承了基類(lèi)的所有屬性和方法,并且只能有一個(gè)基類(lèi),在.NET中System.Object是所有類(lèi)型的最終基類(lèi),這種繼承方式稱(chēng)為實(shí)現(xiàn)繼承。

·接口繼承:派生類(lèi)繼承了接口的方法簽名。不同于實(shí)現(xiàn)繼承的是,接口繼承允許多繼承,同時(shí)派生類(lèi)只繼承了方法簽名而沒(méi)有方法實(shí)現(xiàn),具體的實(shí)現(xiàn)必須在派生類(lèi)中完成。因此,確切地說(shuō),這種繼承方式應(yīng)該稱(chēng)為接口實(shí)現(xiàn)。

CLR支持實(shí)現(xiàn)單繼承和接口多繼承。本節(jié)重點(diǎn)關(guān)注對(duì)象的實(shí)現(xiàn)繼承,關(guān)于接口繼承,我們將在1.5節(jié)“玩轉(zhuǎn)接口”中做詳細(xì)論述。另外,值得關(guān)注的是繼承的可見(jiàn)性問(wèn)題,.NET通過(guò)訪(fǎng)問(wèn)權(quán)限來(lái)實(shí)現(xiàn)不同的控制規(guī)則,這些訪(fǎng)問(wèn)修飾符主要包括:public、protected、internal和private。

下面,我們就以動(dòng)物繼承情況為例,實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的繼承實(shí)例,如圖1-2所示。

圖1-2 動(dòng)物系統(tǒng)UML

在這個(gè)繼承體系中,我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的三層繼承層次,Animal類(lèi)是所有類(lèi)型的基類(lèi),在此將其構(gòu)造為抽象類(lèi),抽象了所有類(lèi)型的普遍特征行為:Eat方法和ShowType方法,其中ShowType方法為虛函數(shù),其具體實(shí)現(xiàn)在子類(lèi)Chicken和Eagle中給出。這種在子類(lèi)中實(shí)現(xiàn)虛函數(shù)的方式,稱(chēng)為方法的動(dòng)態(tài)綁定,是實(shí)現(xiàn)面向?qū)ο罅硪惶匦裕憾鄳B(tài)的基本機(jī)制。另外,Eagle類(lèi)實(shí)現(xiàn)了接口繼承,使得Eagle實(shí)例可以實(shí)現(xiàn)Fly這一特性,接口繼承的優(yōu)點(diǎn)是顯而易見(jiàn)的:通過(guò)IFlyable接口,實(shí)現(xiàn)了對(duì)象與行為的分離,這樣我們無(wú)須擔(dān)心因?yàn)槔^承不當(dāng)而使Chicken有Fly的能力,保護(hù)了系統(tǒng)的完整性。

從圖1-2所示的UML圖中可知,通過(guò)繼承我們輕而易舉地實(shí)現(xiàn)了代碼的復(fù)用和擴(kuò)展,同時(shí)通過(guò)重載(overload)、覆寫(xiě)(override)、接口實(shí)現(xiàn)等方式實(shí)現(xiàn)了封裝變化,隱藏私有信息等面向?qū)ο蟮幕疽?guī)則。通過(guò)繼承,輕易地實(shí)現(xiàn)了子類(lèi)對(duì)父類(lèi)共性的繼承,例如,Animal類(lèi)中實(shí)現(xiàn)了方法Eat(),那么它的所有子類(lèi)就都具有了Eat()特性。同時(shí),子類(lèi)也可以實(shí)現(xiàn)對(duì)基類(lèi)的擴(kuò)展和改寫(xiě),主要有兩種方式:一是通過(guò)在子類(lèi)中添加新方法,例如Bird類(lèi)中就添加了新方法ShowColor用于現(xiàn)實(shí)鳥(niǎo)類(lèi)的毛色;二是通過(guò)對(duì)父類(lèi)方法的重新改寫(xiě),在.NET中稱(chēng)為覆寫(xiě),例如Eagle類(lèi)中的ShowColor()方法。

1.2.3 繼承本質(zhì)論

了解了關(guān)于繼承的基本概念,我們回歸本質(zhì),從編譯器運(yùn)行的角度來(lái)揭示.NET繼承中的運(yùn)行本源,來(lái)發(fā)現(xiàn)子類(lèi)對(duì)象如何實(shí)現(xiàn)對(duì)父類(lèi)成員與方法的繼承,以簡(jiǎn)單的示例揭示繼承的實(shí)質(zhì),來(lái)闡述繼承機(jī)制是如何被執(zhí)行的。

public abstract class Animal
{
   public abstract void ShowType();
   public void Eat()
   {
      Console.WriteLine("Animal always eat.");
   }
}
public class Bird: Animal
{
   private string type = "Bird";
   public override void ShowType()
   {
      Console.WriteLine("Type is {0}", type);
   }
   private string color;
   public string Color
   {
      get { return color; }
      set { color = value; }
   }
}
public class Chicken : Bird
{
   private string type = "Chicken";
   public override void ShowType()
   {
      Console.WriteLine("Type is {0}", type);
   }
   public void ShowColor()
   {
      Console.WriteLine("Color is {0}", Color);
   }
}

然后,在測(cè)試類(lèi)中創(chuàng)建各個(gè)類(lèi)對(duì)象,由于Animal為抽象類(lèi),我們只創(chuàng)建Bird對(duì)象和Chicken對(duì)象。

public class TestInheritance
{
   public static void Main()
   {
      Bird bird = new Bird();
      Chicken chicken = new Chicken();
   }
}

下面我們從編譯角度對(duì)這一簡(jiǎn)單的繼承示例進(jìn)行深入分析,從而了解.NET內(nèi)部是如何實(shí)現(xiàn)我們強(qiáng)調(diào)的繼承機(jī)制的。

(1)我們簡(jiǎn)要地分析一下對(duì)象的創(chuàng)建過(guò)程:

Bird bird = new Bird();

Bird bird創(chuàng)建的是一個(gè)Bird類(lèi)型的引用,而new Bird()完成的是創(chuàng)建Bird對(duì)象,分配內(nèi)存空間和初始化操作,然后將這個(gè)對(duì)象引用賦給bird變量,也就是建立bird變量與Bird對(duì)象的關(guān)聯(lián)。

(2)我們從繼承的角度來(lái)分析CLR在運(yùn)行時(shí)如何執(zhí)行對(duì)象的創(chuàng)建過(guò)程,因?yàn)槔^承的本質(zhì)正體現(xiàn)于對(duì)象的創(chuàng)建過(guò)程中。

在此我們以Chicken對(duì)象的創(chuàng)建為例,首先是字段,對(duì)象一經(jīng)創(chuàng)建,會(huì)首先找到其父類(lèi)Bird,并為其字段分配存儲(chǔ)空間,而B(niǎo)ird也會(huì)繼續(xù)找到其父類(lèi)Animal,為其分配存儲(chǔ)空間,依次類(lèi)推直到遞歸結(jié)束,也就是完成System.Object內(nèi)存分配為止。我們可以在編譯器中用單步執(zhí)行的方法來(lái)大致了解其分配的過(guò)程和順序,因此,對(duì)象的創(chuàng)建過(guò)程是按照順序完成了對(duì)整個(gè)父類(lèi)及其本身字段的內(nèi)存創(chuàng)建,并且字段的存儲(chǔ)順序是由上到下排列,最高層類(lèi)的字段排在最前面。其原因是如果父類(lèi)和子類(lèi)出現(xiàn)了同名字段,則在子類(lèi)對(duì)象創(chuàng)建時(shí),編譯器會(huì)自動(dòng)認(rèn)為這是兩個(gè)不同的字段而加以區(qū)別。

然后,是方法表的創(chuàng)建,必須明確的一點(diǎn)是方法表的創(chuàng)建是類(lèi)第一次加載到AppDomain時(shí)完成的,在對(duì)象創(chuàng)建時(shí)只是將其附加成員TypeHandle指向方法列表在Loader Heap上的地址,將對(duì)象與其動(dòng)態(tài)方法列表相關(guān)聯(lián)起來(lái),因此方法表是先于對(duì)象而存在的。類(lèi)似于字段的創(chuàng)建過(guò)程,方法表的創(chuàng)建也是父類(lèi)在先子類(lèi)在后,原因是顯而易見(jiàn)的,類(lèi)Chicken生成方法列表時(shí),首先將Bird的所有虛方法復(fù)制一份,然后和Chicken本身的方法列表做對(duì)比,如果有覆寫(xiě)的虛方法則以子類(lèi)方法覆蓋同名的父類(lèi)方法,同時(shí)添加子類(lèi)的新方法,從而創(chuàng)建完成Chicken的方法列表。這種創(chuàng)建過(guò)程也是逐層遞歸到Object類(lèi),并且方法列表中也是按照順序排列的,父類(lèi)在前子類(lèi)在后,其原因和字段大同小異,留待讀者自己體味。不言而喻,任何類(lèi)型方法表中,開(kāi)始的4個(gè)方法總是繼承自System.Object類(lèi)型的虛方法,它們是:ToString、Equals、GetHashCode和Finalize,詳見(jiàn)9.1節(jié)“萬(wàn)物歸宗:System.Object”所述。

結(jié)合我們的分析過(guò)程,現(xiàn)在將對(duì)象創(chuàng)建的過(guò)程以圖例來(lái)揭示其在內(nèi)存中的分配情形,如圖1-3所示。

圖1-3 對(duì)象創(chuàng)建內(nèi)存概括

從我們的分析和上面的對(duì)象創(chuàng)建過(guò)程中,我們應(yīng)對(duì)繼承的本質(zhì)有了以下更明確的認(rèn)識(shí):

·繼承是可傳遞的,子類(lèi)是對(duì)父類(lèi)的擴(kuò)展,必須繼承父類(lèi)方法,同時(shí)可以添加新方法。

·子類(lèi)可以調(diào)用父類(lèi)方法和字段,而父類(lèi)不能調(diào)用子類(lèi)方法和字段。

·虛方法如何實(shí)現(xiàn)覆寫(xiě)操作,使得父類(lèi)指針可以指向子類(lèi)對(duì)象成員。

·子類(lèi)不光繼承父類(lèi)的公有成員,同時(shí)繼承了父類(lèi)的私有成員,只是在子類(lèi)中不被訪(fǎng)問(wèn)。

·new關(guān)鍵字在虛方法繼承中的阻斷作用。

你是否已經(jīng)找到了理解繼承、理解動(dòng)態(tài)編譯的不二法門(mén)?

通過(guò)上面的講述與分析,我們基本上對(duì).NET在編譯期的實(shí)現(xiàn)原理有了大致的了解,但是還有以下的問(wèn)題,可能會(huì)引起疑惑,那就是:

Bird bird2 = new Chicken();

這種情況下,bird2.ShowType應(yīng)該返回什么值呢?而bird2.type又該是什么值呢?有兩個(gè)原則,是.NET專(zhuān)門(mén)用于解決這一問(wèn)題的。

·關(guān)注對(duì)象原則:調(diào)用子類(lèi)還是父類(lèi)的方法,取決于創(chuàng)建的對(duì)象是子類(lèi)對(duì)象還是父類(lèi)對(duì)象,而不是它的引用類(lèi)型。例如Bird bird2 = new Chicken()時(shí),我們關(guān)注的是其創(chuàng)建對(duì)象為Chicken類(lèi)型,因此子類(lèi)將繼承父類(lèi)的字段和方法,或者覆寫(xiě)父類(lèi)的虛方法,而不用關(guān)注bird2的引用類(lèi)型是否為Bird。引用類(lèi)型的區(qū)別決定了不同的對(duì)象在方法表中不同的訪(fǎng)問(wèn)權(quán)限。

注意

根據(jù)關(guān)注對(duì)象原則,下面的兩種情況又該如何區(qū)別呢?

Bird bird2 = new Chicken();
Chicken chicken = new Chicken();

根據(jù)上文的分析,bird2對(duì)象和chicken對(duì)象在內(nèi)存布局上是一樣的,差別就在于其引用指針的類(lèi)型不同:bird2為Bird類(lèi)型指針,而chicken為Chicken類(lèi)型指針。以方法調(diào)用為例,不同的類(lèi)型指針在虛擬方法表中有不同的附加信息作為標(biāo)志來(lái)區(qū)別其訪(fǎng)問(wèn)的地址區(qū)域,稱(chēng)為offset。不同類(lèi)型的指針只能在其特定地址區(qū)域內(nèi)執(zhí)行,子類(lèi)覆蓋父類(lèi)時(shí)會(huì)保證其訪(fǎng)問(wèn)地址區(qū)域的一致性,從而解決了不同的類(lèi)型訪(fǎng)問(wèn)具有不同的訪(fǎng)問(wèn)權(quán)限問(wèn)題。

·執(zhí)行就近原則:對(duì)于同名字段或者方法,編譯器是按照其順序查找來(lái)引用的,也就是首先訪(fǎng)問(wèn)離它創(chuàng)建最近的字段或者方法,例如上例中的bird2,是Bird類(lèi)型,因此會(huì)首先訪(fǎng)問(wèn)Bird_type(注意編譯器是不會(huì)重新命名的,在此是為區(qū)分起見(jiàn)),如果type類(lèi)型設(shè)為public,則在此將返回“Bird”值。這也就是為什么在對(duì)象創(chuàng)建時(shí)必須將字段按順序排列,而父類(lèi)要先于子類(lèi)編譯的原因了。

思考

1.上面我們分析到bird2.type的值是“Bird”,那么bird2.ShowType()會(huì)顯示什么值呢?答案是“Type is Chicken”,根據(jù)上面的分析,想想到底為什么?

2.關(guān)于new關(guān)鍵字在虛方法動(dòng)態(tài)調(diào)用中的阻斷作用,也有了更明確的理論基礎(chǔ)。在子類(lèi)方法中,如果標(biāo)記new關(guān)鍵字,則意味著隱藏基類(lèi)實(shí)現(xiàn),其實(shí)就是創(chuàng)建了與父類(lèi)同名的另一個(gè)方法,在編譯中這兩個(gè)方法處于動(dòng)態(tài)方法表的不同地址位置,父類(lèi)方法排在前面,子類(lèi)方法排在后面。

1.2.4 秘境追蹤

通過(guò)對(duì)繼承的基本內(nèi)容的討論和本質(zhì)揭示,是時(shí)候?qū)⑽覀兊难酃廪D(zhuǎn)移到繼承應(yīng)用中的熱點(diǎn)問(wèn)題了,主要是從面向?qū)ο蟮慕嵌葘?duì)繼承進(jìn)行討論,就像追蹤繼承中的秘境,在迷失的森林中尋找出口。

1.實(shí)現(xiàn)繼承與接口繼承

實(shí)現(xiàn)繼承通常情況下表現(xiàn)為對(duì)抽象類(lèi)的繼承,而其與接口繼承在規(guī)則上有以下幾點(diǎn)歸納:

·抽象類(lèi)適合于有族層概念的類(lèi)間關(guān)系,而接口最適合為不同的類(lèi)提供通用功能。

·接口著重于CAN-DO關(guān)系類(lèi)型,而抽象類(lèi)則偏重于IS-A式的關(guān)系。

·接口多定義對(duì)象的行為;抽象類(lèi)多定義對(duì)象的屬性。

·如果預(yù)計(jì)會(huì)出現(xiàn)版本問(wèn)題,可以創(chuàng)建“抽象類(lèi)”。例如,創(chuàng)建了狗(Dog)、雞(Chicken)和鴨(Duck),那么應(yīng)該考慮抽象出動(dòng)物(Animal)來(lái)應(yīng)對(duì)以后可能出現(xiàn)馬和牛的事情。而向接口中添加新成員則會(huì)強(qiáng)制要求修改所有派生類(lèi),并重新編譯,所以版本式的問(wèn)題最好以抽象類(lèi)來(lái)實(shí)現(xiàn)。

·因?yàn)橹殿?lèi)型是密封的,所以只能實(shí)現(xiàn)接口,而不能繼承類(lèi)。

關(guān)于實(shí)現(xiàn)繼承與接口繼承的更詳細(xì)的討論與規(guī)則,請(qǐng)參見(jiàn)8.4節(jié)“面向抽象編程:接口和抽象類(lèi)”。

2.聚合還是繼承,這是個(gè)問(wèn)題

類(lèi)與類(lèi)的關(guān)系,通常有以下幾種情況,我們分別以?xún)蓚€(gè)簡(jiǎn)單類(lèi)Class1和Class2的UML圖來(lái)表示如下。

(1)繼承

如圖1-4所示,Class2繼承自Class1,任何對(duì)基類(lèi)Class1的更改都有可能影響到子類(lèi)Class2,繼承關(guān)系的耦合度較高。

(2)聚合

如圖1-5所示。

圖1-4 繼承關(guān)系

圖1-5 聚合關(guān)系

聚合分為三種類(lèi)型,依次為無(wú)、共享和復(fù)合,其耦合度逐級(jí)遞增。無(wú)聚合類(lèi)型關(guān)系,類(lèi)的雙方彼此不受影響;共享型關(guān)系,Class2不需要對(duì)Class1負(fù)責(zé);而復(fù)合型關(guān)系,Class1會(huì)受控于Class2的更改,因此耦合度更高。總之,聚合關(guān)系是一種HAS-A式的關(guān)系,耦合度沒(méi)有繼承關(guān)系高。

(3)依賴(lài)

依賴(lài)關(guān)系表明,如果Class2被修改,則Class1會(huì)受到影響,如圖1-6所示。

圖1-6 依賴(lài)關(guān)系

通過(guò)上述三類(lèi)關(guān)系的比較,我們知道類(lèi)與類(lèi)之間的關(guān)系,通常以耦合度來(lái)描述,也就是表示類(lèi)與類(lèi)之間的依賴(lài)關(guān)系程度。沒(méi)有耦合關(guān)系的系統(tǒng)是根本不存在的,因?yàn)轭?lèi)與類(lèi)、模塊與模塊、系統(tǒng)與系統(tǒng)之間或多或少要發(fā)生相互交互,設(shè)計(jì)應(yīng)力求將類(lèi)與類(lèi)之間的耦合關(guān)系降到最低。而面向?qū)ο蟮幕驹瓌t之一就是實(shí)現(xiàn)低耦合、高內(nèi)聚的耦合關(guān)系,在2.1節(jié)“OO原則綜述”中所述的合成/聚合復(fù)用原則正是對(duì)這一思想的直接體現(xiàn)。

顯然,將耦合的概念應(yīng)用到繼承機(jī)制上,通常情況下子類(lèi)都會(huì)對(duì)父類(lèi)產(chǎn)生緊密的耦合,對(duì)基類(lèi)的修改往往會(huì)對(duì)子類(lèi)產(chǎn)生一系列的不良反應(yīng)。繼承之毒瘤主要體現(xiàn)在:

·繼承可能造成子類(lèi)的無(wú)限膨脹,不利于類(lèi)體系的維護(hù)和安全。

·繼承的子類(lèi)對(duì)象確定于編譯期,無(wú)法滿(mǎn)足需要運(yùn)行期才確定的情況,而類(lèi)聚合很好地解決了這一問(wèn)題。

·隨著繼承層次的復(fù)雜化和子類(lèi)的多樣化,不可避免地會(huì)出現(xiàn)對(duì)父類(lèi)的無(wú)效繼承或者有害繼承。子類(lèi)部分的繼承父類(lèi)的方法或者屬性,更能適應(yīng)實(shí)際的設(shè)計(jì)需求。

那么,通過(guò)上面的分析,我們深知繼承機(jī)制在滿(mǎn)足更加柔性的需求方面有一些弊端,從而可能造成系統(tǒng)設(shè)計(jì)的漏洞與失衡。解決問(wèn)題的辦法當(dāng)然是多種多樣的,根據(jù)不同的需求進(jìn)行不同的設(shè)計(jì)變更,例如將對(duì)象與行為分離抽象出接口實(shí)現(xiàn)來(lái)避免大基類(lèi)設(shè)計(jì),以聚合代替繼承實(shí)現(xiàn)更柔性的子類(lèi)需求等。

面向?qū)ο蟮幕驹瓌t

多聚合,少繼承。

低耦合,高內(nèi)聚。

聚合與繼承通常體現(xiàn)在設(shè)計(jì)模式的偉大思想中,在此以Adapter模式的兩種方式為例來(lái)比較繼承和聚合的適應(yīng)場(chǎng)合與柔性較量。首先對(duì)Adapter模式進(jìn)行簡(jiǎn)單的介紹。Adapter模式主要用于將一個(gè)類(lèi)的接口轉(zhuǎn)換為另外一個(gè)接口,通常情況下在不改變?cè)畜w系的條件下應(yīng)對(duì)新的需求變化,通過(guò)引入新的適配器類(lèi)來(lái)完成對(duì)既存體系的擴(kuò)展和改造。Adapter模式就其實(shí)現(xiàn)方式主要包括:

·類(lèi)的Adapter模式。通過(guò)引入新的類(lèi)型來(lái)繼承原有類(lèi)型,同時(shí)實(shí)現(xiàn)新加入的接口方法。其缺點(diǎn)是耦合度高,需要引入過(guò)多的新類(lèi)型。

·對(duì)象的Adapter模式。通過(guò)聚合而非繼承的方式來(lái)實(shí)現(xiàn)對(duì)原有系統(tǒng)的擴(kuò)展,松散耦合,較少的新類(lèi)型。

下面,我們回到動(dòng)物體系中,為鳥(niǎo)兒加上鳴叫ToTweet這一行為,為自然界點(diǎn)綴更多美麗的聲音。當(dāng)然不同的鳥(niǎo)叫聲是不同的,雞鳴鷹嘶,各有各的范兒。因此,在Bird類(lèi)的子類(lèi)都應(yīng)該對(duì)ToTweet有不同的實(shí)現(xiàn)。現(xiàn)在我們的要求是在不破壞原有設(shè)計(jì)的基礎(chǔ)上來(lái)為Bird實(shí)現(xiàn)ITweetable接口,理所當(dāng)然,以Adapter模式來(lái)實(shí)現(xiàn)這一需求,通過(guò)類(lèi)的Adapter模式和對(duì)象的Adapter模式兩種方式來(lái)感受其差別。

首先是類(lèi)的Adpater模式,其設(shè)計(jì)UML圖表示為圖1-7。

圖1-7 類(lèi)的Adapter模式

在這一新設(shè)計(jì)體系中,兩個(gè)新類(lèi)型ChickenAdapter和EagleAdapter就是類(lèi)的Adapter模式中新添加的類(lèi),它們分別繼承自原有的類(lèi),從而保留原有類(lèi)型特性與行為,并實(shí)現(xiàn)添加ITweetable接口的新行為T(mén)oTweet()。我們沒(méi)有破壞原有的Bird體系,同時(shí)添加了新的行為,這是繼承的魔力在Adapter模式中的應(yīng)用。我們?cè)诳蛻?hù)端應(yīng)用新的類(lèi)型來(lái)為Chicken調(diào)用新的方法,如圖1-8所示,原有繼承體系中的方法和新的方法對(duì)對(duì)象ca都是可見(jiàn)的。

我們輕松地完成了這一難題,是否該輕松一下?不。事實(shí)上還早著呢,要知道自然界里的鳥(niǎo)兒們都有美麗的歌喉,我們只為Chicken和Eagle配上了鳴叫的行為,那其他成千上萬(wàn)的鳥(niǎo)兒們都有意見(jiàn)了。怎么辦呢?以目前的實(shí)現(xiàn)方式我們不得不為每個(gè)繼承自Bird類(lèi)的子類(lèi)提供相應(yīng)的適配類(lèi),這樣太累了,有沒(méi)有更好的方式呢?

答案是當(dāng)然有,這就是對(duì)象的Adapter模式。類(lèi)的Adapter模式以繼承方式來(lái)實(shí)現(xiàn),而對(duì)象的Adapter模式則以聚合的方式來(lái)完成,詳情如圖1-9所示。

圖1-8 ToTweet方法的智能感知

圖1-9 對(duì)象的Adapter模式

具體的實(shí)現(xiàn)細(xì)節(jié)為:

interface ITweetable
{
   void ToTweet();
}
public class BirdAdapter : ITweetable
{
   private Bird _bird;
   public BirdAdapter(Bird bird)
   {
      _bird = bird;
   }
   public void ShowType()
   {
      _bird.ShowType();
   }
   ……部分省略……
   public void ToTweet()
   {
      //為不同的子類(lèi)實(shí)現(xiàn)不同的ToTweet行為
   }
}

客戶(hù)端調(diào)用為:

public class TestInheritance
{
   public static void Main()
   {
      BirdAdapter ba = new BirdAdapter(new Chicken());
      ba.ShowType();
      ba.ToTweet();
   }
}

現(xiàn)在可以松口氣了,我們以聚合的方式按照對(duì)象的Adapter模式思路來(lái)解決為Bird類(lèi)及其子類(lèi)加入ToTweet()行為的操作,在沒(méi)有添加過(guò)多新類(lèi)型的基礎(chǔ)上十分輕松地解決了這一問(wèn)題。看起來(lái)一切都很完美,新的BirdAdapter類(lèi)與Bird類(lèi)型之間只有松散的耦合關(guān)系而不是緊耦合。

至此,我們以一個(gè)幾乎完整的動(dòng)物體系類(lèi)設(shè)計(jì),基本完成了對(duì)繼承與組合問(wèn)題的探討,系統(tǒng)設(shè)計(jì)是一個(gè)復(fù)雜、兼顧、重構(gòu)的過(guò)程,不管是繼承還是聚合,都是系統(tǒng)設(shè)計(jì)過(guò)程中必不可少的技術(shù)基礎(chǔ),采取什么樣的方式來(lái)實(shí)現(xiàn)完全取決于具體的需求情況。根據(jù)面向?qū)ο蠖嘟M合、少繼承的原則,對(duì)象的Adapter模式更能體現(xiàn)松散的耦合關(guān)系,應(yīng)用更靈活。

1.2.5 規(guī)則制勝

根據(jù)本節(jié)的所有討論,行文至此,我們很有必要對(duì)繼承進(jìn)行歸納總結(jié),將繼承概念中的重點(diǎn)內(nèi)容和重點(diǎn)規(guī)則做系統(tǒng)地梳理,對(duì)我們來(lái)說(shuō)這些規(guī)則條款是掌握繼承的金科玉律,主要包括:

·密封類(lèi)不可以被繼承。

·繼承關(guān)系中,我們更多的是關(guān)注其共性而不是特性,因?yàn)楣残允菍哟螐?fù)用的基礎(chǔ),而特性是系統(tǒng)擴(kuò)展的基點(diǎn)。

·實(shí)現(xiàn)單繼承,接口多繼承。

·從宏觀來(lái)看,繼承多關(guān)注于共通性;而多態(tài)多著眼于差異性。

·繼承的層次應(yīng)該有所控制,否則類(lèi)型之間的關(guān)系維護(hù)會(huì)消耗更多的精力。

·面向?qū)ο笤瓌t:多組合,少繼承;低耦合,高內(nèi)聚。

1.2.6 結(jié)論

在.NET中,如果創(chuàng)建一個(gè)類(lèi),則該類(lèi)總是在繼承。這緣于.NET的面向?qū)ο筇匦裕械念?lèi)型都最終繼承自共同的根System.Object類(lèi)。可見(jiàn),繼承是.NET運(yùn)行機(jī)制的基礎(chǔ)技術(shù)之一,一切皆為對(duì)象,一切皆于繼承。對(duì)于什么是繼承這個(gè)話(huà)題,希望每個(gè)人能從中尋求自己的答案,理解繼承、關(guān)注封裝、品味多態(tài)、玩轉(zhuǎn)接口是理解面向?qū)ο蟮钠瘘c(diǎn),也希望本節(jié)是這一旅程的起點(diǎn)。

主站蜘蛛池模板: 阿拉尔市| 于都县| 察雅县| 安顺市| 秦安县| 永嘉县| 渭南市| 区。| 盖州市| 麻栗坡县| 泰安市| 垦利县| 萨嘎县| 五指山市| 华坪县| 会宁县| 大庆市| 蒙城县| 墨脱县| 龙门县| 资兴市| 余姚市| 洱源县| 梁山县| 昭平县| 陆良县| 舟曲县| 上蔡县| 上犹县| 平顺县| 云安县| 巫溪县| 秭归县| 哈尔滨市| 乐东| 绵阳市| 双流县| 滨州市| 田林县| 崇阳县| 葵青区|