讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議149:依賴抽像而不是實現 >

建議149:依賴抽像而不是實現

在面向過程開發中,我們考慮的是如何實現,依賴的是每個具體實現,而在OOP中,則需要依賴每個接口,而不能依賴具體的實現,比如我們要到北京出差,應該依賴交通工具,而不是依賴的具體飛機或火車,也就是說我們依賴的是交通工具的運輸能力,而不是具體的一架飛機或某一列火車。這樣的依賴可以讓我們實現解耦,保持代碼間的松耦合,提高代碼的復用率,這也是依賴倒置原則(Dependence Inversion Principle,簡稱DIP)提出的要求。

依賴倒置原則的原始定義是:High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions。翻譯過來,包含三層含義:

高層模塊不應該依賴低層模塊,兩者都應該依賴其抽像。

抽像不應該依賴細節。

細節應該依賴抽像。

高層模塊和低層模塊容易理解,每一個邏輯的實現都是由原子邏輯組成的,不可分割的原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊。那什麼是抽像,什麼又是細節呢?在Java語言中,抽像就是指接口或抽像類,兩者都是不能直接被實例化的;而細節就是實現類,實現接口或繼承抽像類而產生的類就是細節,其特點是可以直接被實例化,也就是可以加上一個關鍵字new產生一個對象。依賴倒置原則在Java語言中的表現就是:

模塊間的依賴是通過抽像發生的,實現類之間不發生直接的依賴關係,其依賴關係是通過接口或抽像類產生的。

接口或抽像類不依賴於實現類。

實現類依賴接口或抽像類。

更加精簡的定義就是「面向接口編程」,它的本質就是通過抽像(接口或抽像類)使各個類或模塊的實現彼此獨立,互不影響,從而實現模塊間的松耦合。那我們怎麼在項目中使用這個規則呢?只要遵循以下的幾個規則就可以。

(1)盡量抽像

每個類盡量都有接口或抽像類,或者抽像類和接口兩者都具備。接口和抽像類都是屬於抽像的,有了抽像才可能依賴倒置。

(2)表面類型必須是抽像的

比如定義集合,盡量使用如下這種類型:


List<String>list=new ArrayList<String>();


此時list的表面類型為List,是一個列表的抽像,而實際類型為ArrayList。在可能的情況下,盡量抽像表面類型,但也存在不必抽像的情況,比如工具類xxxUtils,一般是不需要接口或是抽像類的。

(3)任何類都不應該從具體類派生

如果一個項目處於開發狀態,確實不應該有從具體類派生出子類的情況,但這也不是絕對的,因為人都是會犯錯誤的,有設計缺陷是在所難免的,因此只要是不超過兩層的繼承都是可以忍受的。特別是做項目維護時,基本上可以不考慮這個規則,為什麼?維護工作基本上都是做擴展開發的,修復行為通過一個繼承關係,覆寫一個方法就可以修正一個很大的Bug,何必再去繼承最高的基類呢?(當然這種情況盡量發生在不甚瞭解父類或無法獲得父類代碼的情況下)。

(4)盡量不要覆寫基類的方法

如果基類是一個抽像類,而且這個方法已經實現了,那麼子類盡量不要覆寫。類間依賴的是抽像,覆寫了抽像方法,對依賴的穩定性會產生一定的影響。

(5)抽像不關注細節

接口負責定義public屬性和方法,並且聲明與其他對象的依賴關係,抽像類負責公共構造部分的實現,實現類準確地實現了業務邏輯,同時在適當的時候對父類進行了細化,各司其職才能保證抽像的依賴完美實現。

依賴抽像的優點在小型項目中很難體現出來,例如在小於10個人月的項目中,使用簡單的SSH架構,基本上不用費太大力氣就可以完成,是否採用抽像依賴原則影響不大。但是,在一個大中型項目中,採用抽像依賴可以帶來非常多的優點,特別是可以規避一些非技術因素引起的問題。項目越大,需求變化的概率也就越大,通過採用依賴倒置原則設計的接口或抽像類對實現類進行約束,可以減少需求變化引起的工作量劇增的情況。人員的變動在大中型項目中也是時常存在的,如果項目設計優良、代碼結構清晰,人員變化對項目的影響則基本為零。大中型項目的維護週期一般都很長,採用依賴倒置原則可以讓維護人員輕鬆地擴展和維護。