溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中如何消除實現(xiàn)繼承和面向接口編程

發(fā)布時間:2021-07-14 14:48:49 來源:億速云 閱讀:132 作者:chen 欄目:編程語言

本篇內(nèi)容介紹了“Java中如何消除實現(xiàn)繼承和面向接口編程”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

繼承是面向?qū)ο笾泻苤匾母拍?。如果考慮到Java語言特性,繼承分為兩種:接口繼承和實現(xiàn)繼承。這只是技術(shù)層面的問題,即便C++中不存在接口的概念,但它的虛基類實際上也相當(dāng)于接口。對于OO的初學(xué)者來說,他們很希望自己的程序中出現(xiàn)大量的繼承,因為這樣看起來很OO。但濫用繼承會帶來很多問題,盡管有時候我們又不得不使用繼承解決問題。

相比于接口繼承,實現(xiàn)繼承的問題要更多,它會帶來更多的耦合問題。但接口繼承也是有問題的,這是繼承本身的問題。實現(xiàn)繼承的很多問題出于其自身實現(xiàn)上,這也是消除實現(xiàn)繼承的難點,因此這里重點討論實現(xiàn)繼承的問題。

舉個例子(這個例子實在太老套了)。我要實現(xiàn)一個Stack類,我想當(dāng)然地選擇Stack類繼承于ArrayList類(你也可以認(rèn)為我很想OO些或者出于本性的懶惰);現(xiàn)在又有了新的需求,需要實現(xiàn)一個線程安全的Stack,我又定義了一個ConcurrentStack類繼承于Stack并覆蓋了Stack中的部分代碼。

因為Stack繼承于ArrayList,Stack不得不對外暴露出ArrayList所有的public方法,即便其中的某些方法對它可能是不需要的;甚至更糟的是,可能其中的某些方法能改變Stack的狀態(tài),而Stack對這些改變并不知情,這就會造成Stack的邏輯錯誤。

如果我要在ArrayList中添加新的方法,這個方法就有可能在邏輯上破壞它的派生類Stack、 ConcurrentStack。因此在基類(父類)添加方法(修改代碼)時,必須檢查這些修改是否會對派生類產(chǎn)生影響;如果產(chǎn)生影響的話,就不得不對派生類做進(jìn)一步的修改。如果類的繼承體系不是一個人完成的,或者是修改別人的代碼的情況下,很可能因為繼承產(chǎn)生難以覺察的BUG。

問題還是有的。我們有時會見到這樣的基類,它的一些方法只是拋出異常,這意味著如果派生類支持這個方法就重寫它,否則就如父類一樣拋出異常表明其不支持這個方法的調(diào)用。我們也能見到它的一個變種,父類的方法是抽象的,但不是所有的子類都支持這個方法,不支持的方法就以拋出異常的方式表明立場。這種做法是很不友好和很不安全的,它們只能在運行時被“僥幸捕捉”,而很多漏網(wǎng)的異常方法可能會在某一天突然出現(xiàn),讓人不知所措。

引起上面問題的很重要的原因便是基類和派生類之間的耦合。往往只是對基類做了小小的改動,卻不得不重構(gòu)它們的所有的派生類,這就是臭名昭著的“脆弱的基類”問題。由于類之間的關(guān)系是存在的,因此耦合是不可避免的甚至是必要的。但在做OO設(shè)計時,當(dāng)遇到如基類和派生類之間的強耦合關(guān)系,我們就要思量思量,是否一定需要繼承呢?是否會有其他的更優(yōu)雅的替代方案呢?如果一定要學(xué)究的話,你會在很多書中會看到這樣的原則:如果兩個類之間是IS-A關(guān)系,那么就使用繼承;如果兩個類之間是Has-A的關(guān)系,那么就使用委派。很多時候這條原則是適用的,但I(xiàn)S-A并不能做為使用繼承的絕對理由。有時為了消除耦合帶來的問題,使用委派等方法會更好地封裝實現(xiàn)細(xì)節(jié)。繼承有時會對外及向下暴露太多的信息,在GOF的設(shè)計模式中,有很多模式的目的就是為了消除繼承。

關(guān)于何時采用繼承,一個重要的原則是確定方法是否能夠共享。比如DAO ,可以將通用的CRUD 方法定在一個抽象DAO 中,具體的DAO 都派生自這個抽象類。嚴(yán)格的說,抽象DAO 和派生的DAO 實現(xiàn)并不具有IS -A 關(guān)系,我們只是為了避免重復(fù)的方法定義和實現(xiàn)而作出了這一技術(shù)上的選擇。可以說,使用接口還是抽象類的原則是,如果多個派生類的方法內(nèi)容沒有共同的地方,就用接口作為抽象;如果 多個派生類 的方法含有共同的地方,就用抽象類作為抽象。當(dāng)這一原則不適用于接口繼承,如果出現(xiàn)接口繼承,就會相應(yīng)地有實現(xiàn)繼承(基類更多的是抽象類)。

現(xiàn)在說說面向接口編程。在眾多的敏捷方法中,面向接口編程總是被大師們反復(fù)的強調(diào)。面向接口編程,實際上是面向抽象編程,將抽象概念和具體實現(xiàn)相隔離。這一原則使得我們擁有了更高層次的抽象模型,在面對不斷變更的需求時,只要抽象模型做的好,修改代碼就要容易的多。但面向接口編程不意味著非得一個接口對應(yīng)一個類,過多的不必要的接口也可能帶來更多的工作量和維護上的困難。

相比于繼承,OO中多態(tài)的概念要更重要。一個接口可以對應(yīng)多個實現(xiàn)類,對于聲明為接口類型的方法參數(shù)、類的字段,它們要比實現(xiàn)類更易于擴展、穩(wěn)定,這也是多態(tài)的優(yōu)點。假如我以實現(xiàn)類作為方法參數(shù)定義了一個方法void doSomething(ArrayList list),但如果領(lǐng)導(dǎo)哪天覺得 ArrayList不如LinkedList更好用,我將不得不將方法重構(gòu)為void doSomething(LinkedList list),相應(yīng)地要在所有調(diào)用此方法的地方修改參數(shù)類型(很遺憾地,我連對象創(chuàng)建也是采用ArrayList list = new ArrayList()方式,這將大大增加我的修改工作量)。如果領(lǐng)導(dǎo)又覺得用list存儲數(shù)據(jù)不如set好的話,我將再一次重構(gòu)方法,但這一次我變聰明了,我將方法定義為void doSomething(Set set),創(chuàng)建對象的方式改為Set set = new HashSet()。但這樣仍不夠,如果領(lǐng)導(dǎo)又要求將set改回list怎么辦?所以我應(yīng)該將方法重構(gòu)為void doSomething(Collection collection), Collection的抽象程度***,更易于替換具體的實現(xiàn)類。即便需要List或者Set固有的特性,我也可以做向下類型轉(zhuǎn)換解決問題,盡管這樣做并不安全。

面向接口編程最重要的價值在于隱藏實現(xiàn),將抽象的實現(xiàn)細(xì)節(jié)封裝起來而不對外開放,封裝這對于Java EE 中的分層設(shè)計和框架設(shè)計尤其重要。但即便在編程時使用了接口,我們也需要將接口和實現(xiàn)對應(yīng)起來,這就引出如何創(chuàng)建對象的問題。在創(chuàng)建型設(shè)計模式中,單例、工廠方法(模板方法)、抽象工廠等模式都是很好的解決辦法。現(xiàn)在流行的控制反轉(zhuǎn)(也叫依賴注入)模式是以聲明的方式將抽象與實現(xiàn)連接起來,這既減少了單調(diào)的工廠類也更易于單元測試。

做個總結(jié)吧。盡管我竭力批駁繼承的不好鼓吹接口的好,但這并不是絕對的。濫用繼承、濫用接口都會帶來問題。做Java EE開發(fā)的很多朋友抱怨DAO、Service中一個接口一個類的實現(xiàn)方式,盡管它們似乎看起來已成為業(yè)界的***實踐之一。也許排除掉面向接口編程更“瘦”一些,但“瘦”并一定就“好”,需要根據(jù)項目的具體情況而定。關(guān)于繼承和接口的***實踐,各位看官還是需要自身的經(jīng)驗積累和總結(jié)了。

“Java中如何消除實現(xiàn)繼承和面向接口編程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI