從前面對(duì)面向?qū)ο蟮脑O(shè)計(jì)原則的講解,讀者可以了解到,其實(shí)所有的設(shè)計(jì)原則和設(shè)計(jì)模式都離不開(kāi)抽象,因?yàn)橹挥谐橄蟛拍軐?shí)現(xiàn)上述設(shè)計(jì)原則和設(shè)計(jì)模式。
在?Java?中,針對(duì)抽象有兩種實(shí)現(xiàn)方式:一種是接口,一種是抽象類。很多讀者對(duì)這兩種實(shí)現(xiàn)方式比較困惑,到底是使用接口,還是使用抽象類呢?對(duì)于它們的選擇甚至反映出對(duì)問(wèn)題領(lǐng)域本質(zhì)的理解,對(duì)設(shè)計(jì)意圖的理解是否正確、合理?
在面向?qū)ο蟮脑O(shè)計(jì)思想中,所有的對(duì)象都是通過(guò)類來(lái)描繪的,但是反過(guò)來(lái),并不是所有的類都是用來(lái)描繪對(duì)象的,如果一個(gè)類中沒(méi)有描繪一個(gè)具體的對(duì)象,那么這樣的類就是抽象類,抽象類是對(duì)那些看上去不同,但是本質(zhì)上相同的具體概念的抽象,正是因?yàn)槌橄蟮母拍钤趩?wèn)題領(lǐng)域沒(méi)有對(duì)應(yīng)的具體概念,所以抽象類是不能夠?qū)嵗摹?/p>
基本語(yǔ)法區(qū)別
在 Java 中,接口和抽象類的定義語(yǔ)法是不一樣的。這里以動(dòng)物類為例來(lái)說(shuō)明,其中定義接口的示意代碼如下:
publicinterface Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat();
// 所有動(dòng)物都會(huì)飛
publicvoidfly();
}
定義抽象類的示意代碼如下:
publicabstractclass Animal{
// 所有動(dòng)物都會(huì)吃
publicabstractvoideat();
// 所有動(dòng)物都會(huì)飛
publicvoidfly(){};
}
可以看到,在接口內(nèi)只能是功能的定義,而抽象類中則可以包括功能的定義和功能的實(shí)現(xiàn)。在接口中,所有的屬性肯定是 public、static 和 final,所有的方法都是 abstract,所以可以默認(rèn)不寫上述標(biāo)識(shí)符;在抽象類中,既可以包含抽象的定義,也可以包含具體的實(shí)現(xiàn)方法。
在具體的實(shí)現(xiàn)類上,接口和抽象類的實(shí) 現(xiàn)類定義方式也是不一樣的,其中接口實(shí)現(xiàn)類的示意代碼如下:
publicclass concreteAnimalimplements Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
// 所有動(dòng)物都會(huì)飛
publicvoidfly(){}
}
抽象類的實(shí)現(xiàn)類示意代碼如下:
publicclass concreteAnimalextends Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
// 所有動(dòng)物都會(huì)飛
publicvoidfly(){}
}
可以看到,在接口的實(shí)現(xiàn)類中使用 implements 關(guān)鍵字;而在抽象類的實(shí)現(xiàn)類中,則使用 extends 關(guān)鍵字。一個(gè)接口的實(shí)現(xiàn)類可以實(shí)現(xiàn)多個(gè)接口,而一個(gè)抽象類的實(shí)現(xiàn)類則只能實(shí)現(xiàn)一個(gè)抽象類。
設(shè)計(jì)思想?yún)^(qū)別
從前面抽象類的具體實(shí)現(xiàn)類的實(shí)現(xiàn)方式可以看出,其實(shí)在 Java 中,抽象類和具體實(shí)現(xiàn)類之間是一種繼承關(guān)系,也就是說(shuō)如果釆用抽象類的方式,則父類和子類在概念上應(yīng)該是相同的。接口卻不一樣,如果采用接口的方式,則父類和子類在概念上不要求相同。接口只是抽取相互之間沒(méi)有關(guān)系的類的共同特征,而不用關(guān)注類之間的關(guān)系,它可以使沒(méi)有層次關(guān)系的類具有相同的行為。因此,可以這樣說(shuō):抽象類是對(duì)一組具有相同屬性和方法的邏輯上有關(guān)系的事物的一種抽象,而接口則是對(duì)一組具有相同屬性和方法的邏輯上不相關(guān)的事物的一種抽象。
仍然以前面動(dòng)物類的設(shè)計(jì)為例來(lái)說(shuō)明接口和抽象類關(guān)于設(shè)計(jì)思想的區(qū)別,該動(dòng)物類默認(rèn)所有的動(dòng)物都具有吃的功能,其中定義接口的示意代碼如下:
publicinterface Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat();
}
定義抽象類的示意代碼如下:
publicabstractclass Animal{
// 所有動(dòng)物都會(huì)吃
publicabstractvoideat();
}
不管是實(shí)現(xiàn)接口,還是繼承抽象類的具體動(dòng)物,都具有吃的功能,具體的動(dòng)物類的示意代碼如下。
接口實(shí)現(xiàn)類的示意代碼如下:
publicclass concreteAnimalimplements Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
}
抽象類的實(shí)現(xiàn)類示意代碼如下:
publicclass concreteAnimalextends Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
}
當(dāng)然,具體的動(dòng)物類不光具有吃的功能,比如有些動(dòng)物還會(huì)飛,而有些動(dòng)物卻會(huì)游泳,那么該如何設(shè)計(jì)這個(gè)抽象的動(dòng)物類呢?可以別在接口和抽象類中增加飛的功能,其中定義接口的示意代碼如下:
publicinterface Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat();
// 所有動(dòng)物都會(huì)飛
publicvoidfly();
}
定義抽象類的示意代碼如下:
publicabstractclass Animal{
// 所有動(dòng)物都會(huì)吃
publicabstractvoideat();
// 所有動(dòng)物都會(huì)飛
publicvoidfly(){};
}
這樣一來(lái),不管是接口還是抽象類的實(shí)現(xiàn)類,都具有飛的功能,這顯然不能滿足要求,因?yàn)橹挥幸徊糠謩?dòng)物會(huì)飛,而會(huì)飛的卻不一定是動(dòng)物,比如飛機(jī)也會(huì)飛。那該如何設(shè)計(jì)呢?有很多種方案,比如再設(shè)計(jì)一個(gè)動(dòng)物的接口類,該接口具有飛的功能,示意代碼如下:
publicinterface AnimaiFly{
// 所有動(dòng)物都會(huì)飛
publicvoidfly();
}
那些具體的動(dòng)物類,如果有飛的功能的話,除了實(shí)現(xiàn)吃的接口外,再實(shí)現(xiàn)飛的接口,示意代碼如下:
publicclass concreteAnimalimplements Animal,AnimaiFly{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
// 動(dòng)物會(huì)飛
publicvoidfly();
}
那些不需要飛的功能的具體動(dòng)物類只實(shí)現(xiàn)具體吃的功能的接口即可。另外一種解決方案是再設(shè)計(jì)一個(gè)動(dòng)物的抽象類,該抽象類具有飛的功能,示意代碼如下:
publicabstractclass AnimaiFly{
// 動(dòng)物會(huì)飛
publicvoidfly();
}
但此時(shí)沒(méi)有辦法實(shí)現(xiàn)那些既有吃的功能,又有飛的功能的具體動(dòng)物類。因?yàn)樵?Java 中具體的實(shí)現(xiàn)類只能實(shí)現(xiàn)一個(gè)抽象類。一個(gè)折中的解決辦法是,讓這個(gè)具有飛的功能的抽象類,繼承具有吃的功能的抽象類,示意代碼如下:
publicabstractclass AnimaiFlyextends Animal{
// 動(dòng)物會(huì)飛
publicvoidfly();
}
此時(shí),對(duì)那些只需要吃的功能的具體動(dòng)物類來(lái)說(shuō),繼承 Animal 抽象類即可。對(duì)那些既有吃的功能又有飛的功能的具體動(dòng)物類來(lái)說(shuō),則需要繼承 AnimalFly 抽象類。
但此時(shí)對(duì)客戶端有一個(gè)問(wèn)題,那就是不能針對(duì)所有的動(dòng)物類都使用 Animal 抽象類來(lái)進(jìn)行編程,因?yàn)?Animal 抽象類不具有飛的功能,這不符合面向?qū)ο蟮脑O(shè)計(jì)原則,因此這種解決方案其實(shí)是行不通的。
還有另外一種解決方案,即具有吃的功能的抽象動(dòng)物類用抽象類來(lái)實(shí)現(xiàn),而具有飛的功能的類用接口實(shí)現(xiàn);或者具有吃的功能的抽象動(dòng)物類用接口來(lái)實(shí)現(xiàn),而具有飛的功能的類用抽象類實(shí)現(xiàn)。
具有吃的功能的抽象動(dòng)物類用抽象類來(lái)實(shí)現(xiàn),示意代碼如下:
publicabstractclass Animal{
// 所有動(dòng)物都會(huì)吃
publicabstractvoideat();
}
具有飛的功能的類用接口實(shí)現(xiàn),示意代碼如下:
publicinterface AnimaiFly{
// 動(dòng)物會(huì)飛
publicvoidfly();
}
既具有吃的功能又具有飛的功能的具體的動(dòng)物類,則繼承 Animal 動(dòng)物抽象類,實(shí)現(xiàn) AnimalFly 接口,示意代碼如下:
publicclass concreteAnimalextends Animalimplements AnimaiFly{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
// 動(dòng)物會(huì)飛
publicvoidfly();
}
或者具有吃的功能的抽象動(dòng)物類用接口來(lái)實(shí)現(xiàn),示意代碼如下:
publicinterface Animal{
// 所有動(dòng)物都會(huì)吃
publicabstractvoideat();
}
具有飛的功能的類用抽象類實(shí)現(xiàn),示意代碼如下:
publicabstractclass AnimaiFly{
// 動(dòng)物會(huì)飛
publicvoidfly(){};
}
既具有吃的功能又具有飛的功能的具體的動(dòng)物類,則實(shí)現(xiàn) Animal 動(dòng)物類接口,繼承 AnimaiFly 抽象類,示意代碼如下:
publicclass concreteAnimalextends AnimaiFlyimplements Animal{
// 所有動(dòng)物都會(huì)吃
publicvoideat(){}
// 動(dòng)物會(huì)飛
publicvoidfly();
}
這些解決方案有什么不同呢?再回過(guò)頭來(lái)看接口和抽象類的區(qū)別:抽象類是對(duì)一組具有相同屬性和方法的邏輯上有關(guān)系的事物的一種抽象,而接口則是對(duì)一組具有相同屬性和方法的邏輯上不相關(guān)的事物的一種抽象,因此抽象類表示的是“is a”關(guān)系,接口表示的是“l(fā)ike a”關(guān)系。
假設(shè)現(xiàn)在要研究的系統(tǒng)只是動(dòng)物系統(tǒng),如果設(shè)計(jì)人員認(rèn)為對(duì)既具有吃的功能又具有飛的功能的具體的動(dòng)物類來(lái)說(shuō),它和只具有吃的功能的動(dòng)物一樣,都是動(dòng)物,是一組邏輯上有關(guān)系的事物,因此這里應(yīng)該使用抽象類來(lái)抽象具有吃的功能的動(dòng)物類,即繼承 Animal 動(dòng)物抽象類,實(shí)現(xiàn) AnimalFly 接口。
如果設(shè)計(jì)人員認(rèn)為對(duì)既具有吃的功能,又具有飛的功能的具體的動(dòng)物類來(lái)說(shuō),它和只具有飛的功能的動(dòng)物一樣,都是動(dòng)物,是一組邏輯上有關(guān)系的事物,因此這里應(yīng)該使用抽象類來(lái)抽象具有飛的功能的動(dòng)物類,即實(shí)現(xiàn) Animal 動(dòng)物類接口,繼承 AnimaiFly 抽象類。
假設(shè)現(xiàn)在要研究的系統(tǒng)不只是動(dòng)物系統(tǒng),如果設(shè)計(jì)人員認(rèn)為不管是吃的功能,還是飛的功能和動(dòng)物類沒(méi)有什么關(guān)系,因?yàn)轱w機(jī)也會(huì)飛,人也會(huì)吃,則這里應(yīng)該實(shí)現(xiàn)兩個(gè)接口來(lái)分別抽象吃的功能和飛的功能,即除實(shí)現(xiàn)吃的 Animal 接口外,再實(shí)現(xiàn)飛的 AnimalFly 接口。
從上面的分析可以看出,對(duì)于接口和抽象類的選擇,反映出設(shè)計(jì)人員看待問(wèn)題的不同角度,即抽象類用于一組相關(guān)的事物,表示的是“isa”的關(guān)系,而接口用于一組不相關(guān)的事物,表示的是“l(fā)ike a”的關(guān)系。