熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java中消除實現繼承和面向接口的編程

2022-06-13   來源: Java核心技術 

  在匆忙之際理清消除實現繼承和面向接口編程這樣兩個大問題可不是一件容易的事情尤其考慮到自身的認識水平坦白的說這又是一篇炒冷飯的文章但這冷飯又確實不好炒因此在閱讀了這篇文章之後你可要批判地接受(拒絕)我的觀點盡管我的觀點也是來自於別人的觀點

  繼承是面向對象中很重要的概念如果考慮到Java語言特性繼承分為兩種接口繼承和實現繼承這只是技術層面的問題即便C++中不存在接口的概念但它的虛基類實際上也相當於接口對於OO的初學者來說他們很希望自己的程序中出現大量的繼承因為這樣看起來很OO但濫用繼承會帶來很多問題盡管有時候我們又不得不使用繼承解決問題

  相比於接口繼承實現繼承的問題要更多它會帶來更多的耦合問題但接口繼承也是有問題的這是繼承本身的問題實現繼承的很多問題出於其自身實現上因此這裡重點討論實現繼承的問題

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

  因為Stack繼承於ArrayListStack不得不對外暴露出ArrayList所有的public方法即便其中的某些方法對它可能是不需要的甚至更糟的是可能其中的某些方法能改變Stack的狀態而Stack對這些改變並不知情這就會造成Stack的邏輯錯誤

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

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

  引起上面問題的很重要的原因便是基類和派生類之間的耦合往往只是對基類做了小小的改動卻不得不重構它們的所有的派生類這就是臭名昭著的脆弱的基類問題由於類之間的關系是存在的因此耦合是不可避免的甚至是必要的但在做OO設計時當遇到如基類和派生類之間的強耦合關系我們就要思量思量是否一定需要繼承呢?是否會有其他的更優雅的替代方案呢?如果一定要學究的話你會在很多書中會看到這樣的原則如果兩個類之間是ISA關系那麼就使用繼承如果兩個類之間是HasA的關系那麼就使用委派很多時候這條原則是適用的但ISA並不能做為使用繼承的絕對理由有時為了消除耦合帶來的問題使用委派等方法會更好地封裝實現細節繼承有時會對外及向下暴露太多的信息在GOF的設計模式中有很多模式的目的就是為了消除繼承

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

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

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

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

  做個總結吧盡管我竭力批駁繼承的不好鼓吹接口的好但這並不是絕對的濫用繼承濫用接口都會帶來問題做Java EE開發的很多朋友抱怨DAOService中一個接口一個類的實現方式盡管它們似乎看起來已成為業界的最佳實踐之一也許排除掉接口會使程序更一些並一定就需要根據項目的具體情況而定關於繼承和接口的最佳實踐各位看官還是需要自身的經驗積累和總結了


From:http://tw.wingwit.com/Article/program/Java/hx/201311/25674.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.