Java 初學(xué)者——繼承設(shè)計(jì)技巧

       下面給出一些對(duì)設(shè)計(jì)繼承關(guān)系很有幫助的建議:

       (1)將公共操作和域放置在超類(lèi)

       (2)不要使用受保護(hù)的域

       有些程序員認(rèn)為,將大多數(shù)的實(shí)例域定義為protected是一個(gè)不錯(cuò)的主意,只有這樣,子類(lèi)才能夠在需要的時(shí)候直接訪問(wèn)他們。然而,protected機(jī)制并不能夠帶來(lái)更好的保護(hù),其原因主要有兩點(diǎn)。第一,子類(lèi)集合是無(wú)限制的,任何一個(gè)人都能夠由某個(gè)類(lèi)派生一個(gè)子類(lèi),并編寫(xiě)代碼以直接訪問(wèn)protected的實(shí)例域,從而破壞了封裝性。第二,在Java程序設(shè)計(jì)語(yǔ)言中,在同一個(gè)包中的所有類(lèi)都可以訪問(wèn)protected域,而不管它是否為這個(gè)類(lèi)的子類(lèi)。

       (3)使用繼承實(shí)現(xiàn)“is-a”關(guān)系

       使用繼承很容易得到節(jié)省代碼的目的,但有時(shí)候也被人們?yōu)E用了。例如,假設(shè)需要定義一個(gè)鐘點(diǎn)工(Contractor)類(lèi)。鐘點(diǎn)工的信息包含姓名和雇傭日期,但是沒(méi)有薪水。他們按小時(shí)計(jì)薪,并且不會(huì)因?yàn)橥涎訒r(shí)間而獲得加薪。這似乎在誘導(dǎo)人們由Employee派生出子類(lèi)Constractor,然后再增加一個(gè)hourlyWage域。

class Contractor extends Employee

{

       ….

       private double hourlyWage;

}

 

       這并不是一個(gè)好主意。因?yàn)檫@樣一來(lái),每個(gè)鐘點(diǎn)工對(duì)象中都包含了薪水和計(jì)時(shí)工資這兩個(gè)域。在實(shí)現(xiàn)打印支票或稅單方法的時(shí)候,會(huì)到來(lái)無(wú)盡的麻煩,并且會(huì)多些很多代碼。

       鐘點(diǎn)工與雇員之間不屬于“is-a”關(guān)系。鐘點(diǎn)工不是特殊的雇員。

       (4)除非所有繼承的方法都有意義,否則不要使用繼承。

       假設(shè)想編寫(xiě)一個(gè)Holiday類(lèi)。毫無(wú)疑問(wèn),每個(gè)假日也是一日,并且一日可以用GregorianCalendar類(lèi)的實(shí)例表示,因此可以使用繼承。

class Holiday extends GregorianCalendar

{

       ………….

}

 

       很遺憾,在繼承的操作中,假日集不是封閉的。在GregorianCalendar中有一個(gè)共有方法add,可以將假日轉(zhuǎn)換成非假日:

Holiday Christmas;

       christmas.add(Calendar.DAY_OF_MONTH,12);

       因此,繼承對(duì)于這個(gè)例子來(lái)時(shí)并不太適宜。

(5)在覆蓋方法的時(shí)候,不要改變預(yù)期的行為。

       置換原則不僅應(yīng)用于語(yǔ)法,而且也可以應(yīng)用于行為,這似乎更加重要。在覆蓋一個(gè)方法的時(shí)候,不應(yīng)該毫無(wú)緣由的改變行為的內(nèi)涵。就這一點(diǎn)而言,編譯器不會(huì)提供任何幫助,即編譯器不會(huì)檢查重定義的方法是否有意義。例如,可以重定義Holiday類(lèi)中的add方法“修正”原方法的問(wèn)題,或什么也不做,或拋出一個(gè)異常,或繼續(xù)到下一個(gè)假日。然而這些都違反了置換原則,語(yǔ)句序列

int d1=x.get(Calendar.DAY_OF_MONTH);

x.add(Calendar.DAY_OF_MONTH,1);

int d2=x.get(Calendar.DAY_OF_MONTH);

System.out.println(d2-d1);

 

       不管x屬于GregorianCalendar類(lèi),還是屬于Holiday類(lèi),執(zhí)行上述語(yǔ)句后都應(yīng)該得到預(yù)期的行為。

       當(dāng)然,這樣可能會(huì)引起某些爭(zhēng)議。人們可能就預(yù)期行為的含義爭(zhēng)論不休。例如,有些人爭(zhēng)論說(shuō),置換原則要求Manager.equals不處理bonus域,因?yàn)镋mployee.equals沒(méi)有它。實(shí)際上,憑空討論這些問(wèn)題毫無(wú)意義。關(guān)鍵在于,在覆蓋子類(lèi)中的方法時(shí),不要偏離最初的實(shí)際想法。

       (6)使用多態(tài),而非類(lèi)型信息。

       無(wú)論什么時(shí)候,對(duì)于下面這種形式的代碼:

if(x is of type1)

action1(x);

else if (x is of type2)

       action2(x)

 

都應(yīng)該考慮使用多態(tài)性。

action1與 action2表示的是相同的概念嗎?如果是相同的概念,就應(yīng)該為這個(gè)概念定義一個(gè)方法,并將其放置在兩個(gè)類(lèi)的超類(lèi)或接口中,然后,就可以調(diào)用x.action( );以便使用多態(tài)性提供的動(dòng)態(tài)分派機(jī)制執(zhí)行相應(yīng)的動(dòng)作。

使用多態(tài)犯法或接口編寫(xiě)的代碼比使用對(duì)多種類(lèi)型進(jìn)行檢測(cè)的代碼更加易于為何和擴(kuò)展。

(7)不要過(guò)多地使用反射

反射機(jī)制使得人們可以通過(guò)在運(yùn)行時(shí)查看域和方法,讓人們編寫(xiě)出更具有通行的程序。這種功能對(duì)于編寫(xiě)系統(tǒng)程序來(lái)說(shuō)及其實(shí)用,但是通常不是用于編寫(xiě)應(yīng)用程序。反射是很脆弱的,即編譯器很難幫助人們發(fā)現(xiàn)程序中的錯(cuò)誤。任何錯(cuò)誤只能在運(yùn)行時(shí)才被發(fā)現(xiàn),并導(dǎo)致異常。