第 37 講:面向?qū)ο缶幊蹋ň牛撼蓡T繼承控制
前面我們講了繼承控制的基本使用方式和手段,今天我們來(lái)說(shuō)一下,跟成員有關(guān)系的繼承控制方式,以及對(duì)應(yīng)的關(guān)鍵字。
Part 1 abstract
和 override
成員修飾符
前面我們說(shuō)到 abstract
類(lèi)修飾符,表示類(lèi)不再允許實(shí)例化,使用 new
語(yǔ)句產(chǎn)生這個(gè)類(lèi)型的對(duì)象??蓡?wèn)題在于,以類(lèi)為單位控制的繼承實(shí)在是沒(méi)有多大的用途。下面我們來(lái)介紹 abstract
修飾符修飾的成員。
在抽象類(lèi)(abstract class
組合)里,有一種特殊的成員類(lèi)別,叫抽象成員。這是別的類(lèi)型不具備的修飾方式。我們可直接在抽象類(lèi)里,對(duì)某個(gè)或某些成員追加 abstract
修飾符,以表達(dá)這個(gè)成員“我這個(gè)類(lèi)不給實(shí)現(xiàn)邏輯”;而具體的實(shí)現(xiàn)邏輯丟給派生類(lèi)。
這個(gè)思維是我們第一次聽(tīng)說(shuō),因此我們來(lái)舉例說(shuō)明。假設(shè)我有一個(gè) Shape
的抽象類(lèi),表示一個(gè)形狀。顯然,形狀肯定會(huì)有一些相關(guān)的屬性信息,比如說(shuō)形狀本身的面積。假設(shè)我們定為 Area
。那么我們創(chuàng)建一些子類(lèi)型,從 Shape
類(lèi)型派生。
我們創(chuàng)建了兩個(gè)子類(lèi)型 Square
和 Circle
類(lèi)型,從 Shape
派生。在 Shape
里有一個(gè) Area
屬性??梢宰⒁庹Z(yǔ)法不是很好理解的地方在于這個(gè) get
后直接跟了一個(gè)分號(hào)。這是正確的寫(xiě)法,是因?yàn)檫@個(gè) Area
的 get
方法(取值器)目前是無(wú)法寫(xiě)出來(lái)的,因?yàn)槲叶疾恢肋@個(gè) Shape
到底需要什么信息才可計(jì)算面積。比如正方形需要一個(gè)參數(shù)(邊長(zhǎng))就可以計(jì)算面積;但長(zhǎng)方形需要兩個(gè)參數(shù)(長(zhǎng)和寬)才可以計(jì)算;梯形則需要三個(gè)參數(shù)(上底、下底和高)。
因?yàn)閰?shù)不定,所以我們無(wú)法從基類(lèi)型 Shape
上給出代碼實(shí)現(xiàn)。于是,我們定成抽象屬性,以表達(dá)這個(gè)屬性只能且必須在子類(lèi)型是實(shí)現(xiàn)。
在子類(lèi)型 和 Circle
里,我們不得不對(duì) Area
給出實(shí)現(xiàn)的代碼。另外,我們要對(duì)上面的抽象類(lèi)型的 Area
屬性實(shí)現(xiàn)執(zhí)行代碼,因此我們需要在這個(gè)屬性前面追加 override
關(guān)鍵字。override
專(zhuān)門(mén)用來(lái)表達(dá)“我是把基類(lèi)型的同名成員拿來(lái)使用和修改的”,在一般情況上是不使用這個(gè)關(guān)鍵字的。
const
修飾符可修飾一個(gè)系統(tǒng)自帶的那些數(shù)據(jù)類(lèi)型的字面量。它和readonly
目前的唯一區(qū)別是,const
只能修飾一個(gè)字面量賦值過(guò)來(lái)的字段,且不能修飾static
修飾符;而readonly
可以修飾任何數(shù)據(jù)類(lèi)型賦值過(guò)來(lái)的字段,且可以有static
也可以沒(méi)有static
。
Part 2 sealed
和 override
成員修飾符
正相反的是,如果類(lèi)型給出了實(shí)現(xiàn),那么就需要用 override
修飾符對(duì)成員進(jìn)行修飾。但是,override
修飾后的成員還可繼續(xù)在往下繼承的類(lèi)型里被重寫(xiě)和修改邏輯。比如說(shuō) A
類(lèi)型有個(gè) Prop
屬性,是 abstract
修飾的;接著 B
從 A
類(lèi)型派生,那么 Prop
屬性在 B
類(lèi)型里就必須重寫(xiě)和修改邏輯,因此要標(biāo)記 override
修飾符??衫^承是可以不限制的,所以如果還有個(gè) C
類(lèi)型走 B
類(lèi)型派生的話,那么 C
類(lèi)型里也可重寫(xiě) B
類(lèi)型里的 Prop
的實(shí)現(xiàn)邏輯。
假設(shè) B
類(lèi)型給出的實(shí)現(xiàn)不可變動(dòng),不讓后續(xù)繼承的時(shí)候修改的話,我們可使用 sealed
關(guān)鍵字標(biāo)記這個(gè)成員(構(gòu)成 sealed override
的關(guān)鍵字組合),來(lái)表達(dá)這個(gè)成員是重寫(xiě)的,但在這里重寫(xiě)后,后續(xù)只能使用這個(gè)成員,而不可修改執(zhí)行邏輯。
比如這樣寫(xiě)代碼的話,C
類(lèi)型里給出 Prop
的實(shí)現(xiàn),因?yàn)?C
從 B
類(lèi)型派生,但 B
類(lèi)型里 Prop
屬性就標(biāo)記了 sealed
關(guān)鍵字了,因此這個(gè)成員不再允許后續(xù)繼承的時(shí)候再次重寫(xiě)了,所以 C
類(lèi)型里 Prop
的屬性就會(huì)產(chǎn)生編譯器錯(cuò)誤,編譯器會(huì)提示你“不能這么用”。
Part 3 virtual
和 override
成員修飾符
abstract
的好處在于,可以把成員作為單位提供抽象的書(shū)寫(xiě)模式,編譯器就會(huì)自動(dòng)提示我們讓我們?cè)谧宇?lèi)型里實(shí)現(xiàn)它們,不論對(duì)于代碼實(shí)現(xiàn)來(lái)說(shuō),還是代碼書(shū)寫(xiě)來(lái)說(shuō),都非常方便。但是,abstract
有一個(gè)問(wèn)題是,如果我基類(lèi)型想要給出默認(rèn)的實(shí)現(xiàn)代碼的話(而不是一定非要丟給派生類(lèi)來(lái)實(shí)現(xiàn),我們可先給出一個(gè)默認(rèn)實(shí)現(xiàn),然后派生類(lèi)按自己是否需要修改來(lái)決定到底需不需要 override
重寫(xiě)和修改掉原始的邏輯)。
比如說(shuō)前面舉的例子 Car
(汽車(chē))類(lèi)型和 ConvertibleCar
(敞篷跑車(chē))類(lèi)型。我們顯然可以給 Car
提供一個(gè)默認(rèn)的實(shí)現(xiàn)代碼來(lái)輸出汽車(chē)的具體描述。
這樣寫(xiě)肯定是沒(méi)錯(cuò)的,但問(wèn)題就在于這里的 new
關(guān)鍵字改變了繼承的關(guān)系。new
關(guān)鍵字是表示“我子類(lèi)型里提供一個(gè)同名的方法,而這個(gè)方法和基類(lèi)型的那個(gè)方法沒(méi)有任何關(guān)系”。這怎么可能?基類(lèi)型的 GetDescription
顯然是照搬下來(lái)并重新修改邏輯才對(duì)。因此,我們這里必須改用 override
修飾符。
問(wèn)題來(lái)了?;?lèi)型的 GetDescription
成員并沒(méi)有修飾 abstract
,因?yàn)樗旧砭褪亲詭Я藞?zhí)行的邏輯的,如果我們給子類(lèi)型的 GetDescription
方法標(biāo)記 override
這不就是錯(cuò)了么。
那么,怎么解決兩邊都沒(méi)辦法解決的問(wèn)題呢?我們只需要在基類(lèi)型 Car
里的 GetDescription
方法上標(biāo)記 virtual
,以表達(dá)“這個(gè)方法可提供給派生類(lèi)型重寫(xiě)和修改邏輯”。
這樣的話,基類(lèi)型 Car
里的 GetDescription
方法就表示“我有自己的實(shí)現(xiàn)代碼,你可以用也可以修改邏輯”。如果你需要修改執(zhí)行代碼的話,這里你需要為子類(lèi)型重寫(xiě)的這個(gè)同名方法 GetDescription
上追加 override
修飾符,來(lái)表示“我是確確實(shí)實(shí)是從基類(lèi)型里拿下來(lái)的成員,且要修改執(zhí)行邏輯的”。
Part 4 使用 base
對(duì)象調(diào)用基類(lèi)型成員的執(zhí)行邏輯
假設(shè)一種情況。我這里有兩個(gè)類(lèi),一個(gè)叫做 Person
,表示一個(gè)人,而另外有一個(gè)類(lèi)叫 Employee
類(lèi),表示雇員(社會(huì)人)。
那么,請(qǐng)注意最下面的 Main
方法,執(zhí)行了 e
實(shí)例(Employee
類(lèi)型的實(shí)例)的 GetInfo
方法,會(huì)輸出什么呢?
我們定位到 Employee
里面的 GetInfo
方法上去。里面寫(xiě)了一句 base.GetInfo()
,還記得之前說(shuō)過(guò)的 base
關(guān)鍵字嗎?是的,base
關(guān)鍵字用來(lái)表示基類(lèi)型的成員的使用。但是因?yàn)榍懊鏇](méi)有提到繼承的控制,因此 base
和 this
的效果是一樣的。不過(guò)這里不一樣的地方是,因?yàn)?GetInfo
是被重寫(xiě)過(guò)的,因此寫(xiě)的 base.GetInfo()
一定指的是基類(lèi)型的那個(gè) GetInfo
。因此,這句話等效于把基類(lèi)型的 GetInfo
的執(zhí)行代碼直接給抄下來(lái)。
因此,整體輸出三句話:第一個(gè)是輸出 name
,第二個(gè)是輸出 ssn
,最后是重寫(xiě)的 GetInfo
方法里輸出的 id
。
Part 5 一些問(wèn)題
下面針對(duì)于前文介紹和的內(nèi)容,給出一些可能你有疑惑的問(wèn)題。
5-1 abstract
、sealed
和 virtual
修飾符的適用范圍
顯然,并非所有成員都可以修飾這些東西。比如說(shuō)字段。字段并不是一種執(zhí)行邏輯,而是一個(gè)數(shù)值。而 static
修飾過(guò)的成員也都不能使用這些關(guān)鍵字。因?yàn)槔^承機(jī)制顯然是跟對(duì)象綁定起來(lái)才有意義。比如,“工人”和“老師”可能不是同一個(gè)類(lèi)型的實(shí)例,比如有可能“工人”是 Worker
類(lèi)型的,而“老師”則可能是 Teacher
類(lèi)型的。但是,它們倆都從 Person
類(lèi)型派生下來(lái),這個(gè) Person
類(lèi)型可能包含了一些可以使用的成員,比如 Eat
方法表示吃飯過(guò)程;Sleep
方法表示睡覺(jué)過(guò)程;GoShopping
表示購(gòu)物血拼的過(guò)程。但是,如果是靜態(tài)的成員的話,這一切就顯得沒(méi)有意義。因?yàn)殪o態(tài)的話,并不會(huì)在邏輯上跟一個(gè)單獨(dú)的對(duì)象綁定起來(lái),此時(shí)說(shuō)什么吃飯購(gòu)物都顯得沒(méi)有了意義,那你還抽象、重寫(xiě)它們干什么呢?
因此,這三個(gè)修飾符的適用范圍顯然是:
不能是字段;
不能被
static
修飾符過(guò)的成員。
5-2 override
和 new
的區(qū)別
可以看到,前面三個(gè)小節(jié)分別說(shuō)的是 abstract
、sealed
和 virtual
關(guān)鍵字,但因?yàn)樗鼈兌伎纱钆?override
關(guān)鍵字一并使用,所以標(biāo)題里全都帶有 override
修飾符這個(gè)東西。而前面我們也說(shuō)了 new
修飾符。由于它們倆的功能看起來(lái)差不太多,所以下面我們來(lái)說(shuō)一下這兩個(gè)關(guān)鍵字的區(qū)別。
override
修飾符:這個(gè)成員是我從基類(lèi)型拿下來(lái)的同名成員,因此我這里是用來(lái)修改原始類(lèi)型成員的一種機(jī)制;new
修飾符:這個(gè)成員和基類(lèi)型成員的實(shí)現(xiàn)沒(méi)有半點(diǎn)關(guān)系。我這里給出的實(shí)現(xiàn)會(huì)讓基類(lèi)型這個(gè)成員完全被覆蓋掉,阻斷了這個(gè)成員在整個(gè)繼承鏈上的繼承情況。
要知道,繼承關(guān)系是可無(wú)限往下的,A
可以派生出 B
,然后 B
還能派生出 C
,甚至 C
還能派生出 D
類(lèi)型。如果在 B
類(lèi)型里用到 override
關(guān)鍵字修飾了某個(gè)成員,就表達(dá) B
是重寫(xiě)了 A
的這個(gè)成員,修改掉了里面的邏輯;但是如果 B
類(lèi)型某個(gè)成員用的是 new
關(guān)鍵字,那么 A
類(lèi)型的這個(gè)同名成員和 B
這個(gè)成員一點(diǎn)關(guān)系都沒(méi)有了。以后,C
和 D
類(lèi)型里的這個(gè)成員都以 B
這個(gè)成員為準(zhǔn),而 A
里的同名成員被 B
類(lèi)型用 new
修飾符覆蓋掉了。