1.類的繼承與多態(tài)性1.1基類和擴充類C#實現(xiàn)繼承的方式:類繼承和接口繼承 繼承用冒號(:)表示。被繼承的叫做父類或者基類,從基類繼承的類叫擴充類,又叫派生類或子類。所有類的基類System.Object 聲明方式:[訪問修飾符] class 擴充類名稱:基類名稱 { } 若B繼承自A,也可以使用強制轉(zhuǎn)換操作將其轉(zhuǎn)換為A 對象。如: A b = (A)new B();或者 A b = new B();此時,B對象將被限制為A 對象的數(shù)據(jù)和行為,而無法再訪問B對象中的數(shù)據(jù)和行為,除非A中的方法被B重載,將會訪問B 的方法。將B強制轉(zhuǎn)換為A后,還可以將A重新轉(zhuǎn)換為B,但是,并非A的所有實例在任何情況下都可強制轉(zhuǎn)換為B,只有實際上是B的實例的那些實例才可以強制轉(zhuǎn)換為B。 擴充類不能繼承基類中定義的private方法,只能繼承基類的public成員或者protected成員。 初始化基類時,會首先調(diào)用基類的構(gòu)造函數(shù)再調(diào)用擴充類的構(gòu)造函數(shù) 1.2 多態(tài)性定義:同一操作作用于不同類的實例,不同的類將進行不同的解釋,最后產(chǎn)生不同的結(jié)果。即:建立一個父類的對象,它的內(nèi)容可以是這個父類的,也可以是這個子類的,子類和父類都定義有同樣的方法;當(dāng)使用對象調(diào)用這個方法的時候,父類里的同名對象將被調(diào)用,當(dāng)在父類的這個方法前加virtual關(guān)鍵字,子類的同名方法將被調(diào)用。 實現(xiàn)多態(tài)的方法: 1.通過繼承實現(xiàn)。多個類可以繼承自同一個類,每個擴充類可根據(jù)重寫基類成員以提供不同的功能。 2.通過抽象類實現(xiàn)。抽象類本身不能被實例化,只能在擴充類中通過繼承使用。抽象類的部分或全部成員不一定都要實現(xiàn),但要在繼承類中全部實現(xiàn),抽象類中已實現(xiàn)的成員扔可以被重寫,并且繼承類仍可以實現(xiàn)其他功能。 3.通過接口實現(xiàn)。接口僅聲明類需要實現(xiàn)的方法、屬性、事件;以及每個成員需要接受和返回的參數(shù)類型,而他們的特定實現(xiàn)需要實現(xiàn)類去完成。
C#中,如果基類的某個功能不滿足要求,可以在擴充類中重寫基類的方法實現(xiàn)新的功能, 1 class A 注意:(1)虛擬方法不能聲明 為靜態(tài)的。因為靜態(tài)的方法是應(yīng)用在類一層次的,而面向?qū)ο蟮亩鄳B(tài)性只能在對象上運作,所以無法在類中使用。此外,override,abstract也不能跟static共存. (2)virtual 不能喝private 一起使用,否則無法在擴充類中重寫。 (3)重寫方法的名稱、參數(shù)個數(shù)、類型以及返回值都必須和虛擬方法一致。
只有在擴充類中使用override修飾符時,才能重寫基類聲明為virtual的方法;否則,在繼承的類中聲明一個與基類方法同名的方法會隱藏基類的方法。 與方法或?qū)傩圆煌氖?,使用new關(guān)鍵字時并不要求基類中的方法或?qū)傩月暶鳛関irtual,只要在擴充類的方法或?qū)傩郧奥暶鳛閚ew,就可以隱藏基類的方法或?qū)傩?。理解:新方法把老方法(相對于子類)隱藏,但老方法仍可見。 舉例: 父:實例:b, 子:實例 d; (1)父方法:無virtual;子方法:new;此時若強制轉(zhuǎn)換b=d,b.方法 調(diào)用父中方法 (2)父方法:有virtual;子方法:new;此時若強制轉(zhuǎn)換b=d,b.方法 調(diào)用父中方法 (3)父方法:有virtual;子方法:override;此時若強制轉(zhuǎn)換b=d,b.方法 調(diào)用子類中方法 什么時候用隱藏呢? 如開發(fā)人員A需要重新設(shè)計基類中的某個方法,該基類是一年前由另一組開發(fā)人員設(shè)計的,并且已經(jīng)交給用戶使用,可是原來的開發(fā)人員在方法前沒有加virtual關(guān)鍵字,但又不允許修改原來的程序,這是無法用override重寫基類的方法,這是就需要隱藏基類的方法。
可以在擴充類中用關(guān)鍵字base直接調(diào)用基類的方法。 1 class B 1.3抽象類使用abstract修飾符,若類中的方法或?qū)傩詾?/strong>abstract,類必須用abstract修飾。只能用作基類,抽象方法沒有實現(xiàn)代碼(無大括號!) 抽象類和非抽象類的區(qū)別: (1)抽象類不能直接被實例化,只能在擴充類中通過繼承使用 (2)抽象類可以包含抽象成員,非抽象類不能包含抽象成員 當(dāng)從抽象類派生非抽象類時,非抽象類必須實現(xiàn)抽象類的所有(必須是所有?。?/strong>抽象成員,當(dāng)然,如果繼承抽象類的也是抽象類就不必實現(xiàn)其抽象成員,由最后一個非抽象類實現(xiàn)。 1 abstract class B 什么時候使用抽象類呢? 如果有一個通用方法,對所有擴充類來說都是公共的,并且強制要求所有擴充類都必須實現(xiàn)這個方法,這種情況下就可以把該方法定義為基類中的抽象方法。 1.4 密封類密封類是不能被其他類繼承的類,用sealed關(guān)鍵字聲明。sealed關(guān)鍵字也可以限制基類中的方法,防止被擴充類重寫,若類中的方法是sealed,該類不是必須用sealed來修飾的。帶有sealed修飾符的方法稱為密封方法,sealed方法必須和override關(guān)鍵字一起使用。 一般情況下,只有在某個方法重寫了基類中同名的方法,但又不希望該方法所在的類的擴充類重寫該方法,就可以在該方法前使用修飾符sealed。 1 abstract class B 1.5 繼承過程中構(gòu)造函數(shù)的處理在擴充類中,繼承了所有基類中聲明的public或protected成員。但是構(gòu)造函數(shù)則排除在外,不會被繼承下來。 為什么不繼承基類的構(gòu)造函數(shù)呢?因為構(gòu)造函數(shù)的主要用途是對類的成員初始化,包括對私有成員的初始化。如果讓構(gòu)造函數(shù)也能繼承,由于擴充類中無法訪問基類的私有成員,因此會導(dǎo)致創(chuàng)建擴充類的實例時無法對基類的私有成員進行初始化工作,從而導(dǎo)致程序運行失敗。為了解決這個問題,c#在內(nèi)部按照下列順序處理構(gòu)造函數(shù):從擴充類依次向上尋找基類,直到找到最初的基類,然后開始執(zhí)行最初的基類的構(gòu)造函數(shù)、再依次向下執(zhí)行擴充類的構(gòu)造函數(shù),直至執(zhí)行完最終的擴充類的構(gòu)造函數(shù)為止。 如下代碼會發(fā)生“A方法沒有0個參數(shù)的重載”錯誤,這是因為創(chuàng)建B的實例時,編譯器會尋找基類A中提供的無參數(shù)的構(gòu)造函數(shù),而A中沒有,從而報錯。 1 class Program 要解決這個問題,需將public B(int age1)改為public B(int age1):base(age1)即可。程序執(zhí)行時,會先調(diào)用System.Object的構(gòu)造函數(shù),然后調(diào)用A的帶參數(shù)的構(gòu)造函數(shù),由于B的構(gòu)造函數(shù)已經(jīng)將age1參數(shù)傳遞給A,所以A的構(gòu)造函數(shù)就可以利用這個傳遞的參數(shù)進行初始化。 2.版本控制所有的方法默認(rèn)都是非虛擬的,調(diào)用非虛擬方法時不會受到版本的影響。若基類中聲明一個虛擬方法,而擴充類的方法中使用了override關(guān)鍵字,則執(zhí)行時會調(diào)用擴充類中的方法;若沒有使用override關(guān)鍵字,則調(diào)用基類的方法。而沒有聲明為virtual的非虛擬方法,則在編譯時就確定了應(yīng)該調(diào)用哪個方法了。 View Code
另外,若B繼承自A,C繼承自B,那么A中的virtual方法,B中override后,C中還可以繼續(xù)override。即 被override修飾的方法后就默認(rèn)為virtual,即virtual具有傳遞性。 3.接口接口像一個抽象類,不過,接口是完全抽象的集合成員,主要特點是 只有聲明部分,沒有實現(xiàn)部分。一般用I開頭,接口中只能包含:(1)方法 屬性 索引器和事件的聲明,(2)不能包含構(gòu)造函數(shù)(因為無法構(gòu)建不能實例化的對象)(3)不能包含字段(因為字段隱含了某系內(nèi)部的執(zhí)行方法)(4)不能包含任何實現(xiàn)代碼(5)接口中的方法必須都是public的,因此不能再用public修飾符聲明。 接口的用途是表示設(shè)計者和調(diào)用者的一種約定。例如,提供某個方法用什么名字、需要哪些參數(shù)、以及每個參數(shù)的類型。抽象類和接口的一個主要差別:類可以繼承多個接口,但只能繼承一個抽象類。 使用接口還是抽象類來為組件提供多態(tài)性主要考慮以下幾個方面: (1)如果預(yù)計要創(chuàng)建組件的多個版本,則創(chuàng)建抽象類。抽象類提供簡單易行的方法來控制組件版本。通過更新基類,是所有繼承類都自動更新。另一方面,為了保護為使用接口而編寫的現(xiàn)有系統(tǒng),要求接口一旦創(chuàng)建就不能更改。如果需要接口的新版本,必須創(chuàng)建一個新的接口 (2)如果創(chuàng)建的功能將在大范圍的完全不同的對象間使用,則使用接口。抽象類應(yīng)主要用于關(guān)系密切的對象,而接口最適合為不相關(guān)的類提供通用功能。 (3)如果要設(shè)計小而精煉的功能塊,則使用接口;如果要設(shè)計大的功能單元,則使用抽象類,設(shè)計優(yōu)良的接口往往很小且相互獨立,減少了發(fā)生性能問題的可能。 (4)如果要在組件的所有實現(xiàn)間提供通用的已實現(xiàn)的功能,則使用抽象類,抽象類允許部分實現(xiàn)類,而接口不包含任何成員的實現(xiàn)。 接口的聲明與實現(xiàn): 聲明: [訪問修飾符] interface 接口名稱 { //接口體 } 實現(xiàn)某個接口的任何類都將擁有該接口中所有元素,因此,當(dāng)需要再不相關(guān)的類中實現(xiàn)同樣的功能時,就可以使用接口。 View Code
顯示方式實現(xiàn)接口: 由于不同接口中的方法可以重名,因此,在一個類中實現(xiàn)接口中的方法時就存在著多義性,這時可以顯示實現(xiàn)接口中的方法,對于顯示實現(xiàn)的方法,不能通過類的實例訪問,而必須使用接口的實例。 View Code
小總結(jié) 封裝優(yōu)點:
繼承優(yōu)點: 1.使得所有子類公共的部分都放在了父類,使得代碼得到了共享,就避免了重復(fù) 2.繼承可使得修改或擴展繼承而來的實現(xiàn)代碼都較為容易。 缺點: 父類變,子類不得不變,繼承會破壞包裝,父類實現(xiàn)細(xì)節(jié)暴露給子類,其實是增大了兩個類之間的耦合性。 注意:c#中,子類從它的父類中繼承的成員有方法,域,屬性,事件,索引指示器,但構(gòu)造方法只能被調(diào)用不能繼承??梢杂胋ase關(guān)鍵字調(diào)用父類的成員。當(dāng)兩個類之間具備“is-a”關(guān)系時,就可以考慮用繼承。
多態(tài)特點: 1.子類以父類的身份出現(xiàn) 2.子類在工作時以自己的方式來實現(xiàn) 3.子類以父類身份出現(xiàn)時,子類特有的屬性和方法不可以使用 要實現(xiàn)多態(tài),要將父類聲明為虛方法,子類將方法重寫:override 注意: 重載:一般在類內(nèi)部,在不改變原方法的基礎(chǔ)上,增加新功能;兩個方法必須要方法名相同,但參數(shù)類型或個數(shù)必須要有所不同。 重寫:一般在不同的類中,必須要跟重寫的方法返回類型及參數(shù)個數(shù)及類型都相同,需要加override關(guān)鍵字,被重寫的方法要加 virtual關(guān)鍵字。
抽象類和接口的區(qū)別: 表面上看:1.抽象類可以給出一些成員的實現(xiàn),而接口不包含任何成員的實現(xiàn) 2.抽象類的抽象成員可被子類部分實現(xiàn),而接口的成員需要實現(xiàn)類完全實現(xiàn) 3.一個類只能繼承一個抽象類,但可實現(xiàn)多個接口 深層次: 1 .類是對對象的抽象,抽象類是對類的抽象(類相關(guān)性),接口是對行為的抽象(行為相關(guān)性) 2.對于行為跨越不同類的對象,可使用接口;對于一些相似的類對象,用繼承抽象類 3.從設(shè)計角度講,抽象類是從子類中發(fā)現(xiàn)了公共的東西,泛化出父類,然后子類繼承父類;而接口是根本不知子類的存在,方法如何實現(xiàn)還不確定,預(yù)先定義。 抽象類往往是通過 重構(gòu) 得來的,是自底而上抽象出來的,而接口則是自頂向下設(shè)計出來的。 |
|