控制反轉(Inversion Of Controller)的一個著名的同義原則是由Robert C.Martin提出的依賴倒置原則(Dependency Inversion Principle),它的另一個昵稱是好萊塢原則(Hollywood Principle):不要找我們,讓我們來找你。
依賴和耦合(Dependency and Coupling)
依賴:依賴描述了兩個模型元素之間的關系,如果被依賴的模型元素發生變化就會影響到另一個模型元素。
簡單的說,依賴就是一種需要。魚需要水才能生存,魚對水就有依賴關系;人需要進食才能活着,人對食物就有依賴關系。
耦合:如果改變程序的一個模塊要求另一個模塊同時發生變化,就認為這兩個模塊發生了耦合。
簡單地說,耦合就是發生了依賴。和上面的例子一樣,魚和水之間發生了耦合,如果水發生了改變,會影響到魚;人和食物之間發生了耦合,如果食物發生了改變,也會對人造成影響。
從上面的定義中可以看出,如果模塊A調用模塊B提供的方法,或者訪問模塊B中的某些數據成員(當然,在面向對象開發中一般不提倡這樣做),我們就認為模塊A依賴於模塊B,模塊A和模塊B之間發生了耦合。
class A { B b = new B(); // 對B產生了依賴關系,即A和B發生了耦合關系 void doA() { b.doB(); } } class B { void doB() { // do something } }
那么,依賴對於我們來說到底是好事還是壞事呢?
由於人類的理解力有限,大多數人難以理解和把握過於復雜的系統(大神或天才除外),因此,把軟件系統划分為多個模塊,可以有效控制模塊的復雜度,使每個模塊都易於理解和維護。但在這種情況下,模塊之間就必須以某種方式交換信息,也就是說,必然要發生某種耦合關系。如果某個模塊和其它模塊沒有任何關聯(哪怕是潛在的或隱含的依賴關系),我們就幾乎可以斷定,該模塊不屬於此軟件系統,應該從系統中剔除。如果所有模塊之間都沒有任何耦合關系,其必然導致一個結果:整個軟件不過是多個互不相干的系統的簡單堆積,對每個系統而言,所有功能還是要在一個模塊中實現,相當於沒有做任何模塊的分解。
因此,模塊之間必然會有這樣或那樣的依賴關系,永遠不要幻想消除所有依賴,但是,過強的耦合關系(如,一個模塊的變化,會造成一個或多個其他模塊也同時發生變化的依賴關系),會對軟件系統的質量造成很大的危害。特別是當需求發生變化時,代碼的維護成本將非常高。多以,我們必須想盡辦法來控制和消除不必要的耦合,特別是那些會導致其它模塊發生不可控變化的依賴關系。
依賴倒置、控制反轉、依賴注入等原則,就是人們在和依賴關系進行艱苦斗爭的過程中不斷產生和發展起來的。
接口和實現分離(Interface And Implement)
把接口和實現分開是人們試圖控制依賴關系的第一個嘗試。
Java語言提供了純粹的接口類(Interface),這種接口類不包含任何實現代碼,只是定義了要做什么功能,具體的實現代碼寫在實現該接口類的實現類(Implement)中。調用者只需要關心接口能做什么功能,而不用關心功能具體是怎么實現的。
interface PayInterface { // 定義接口 void payOnline(); // 定義一個抽象方法 } class PayImplement implements PayInterface { // 定義實現 void payOnline() { // 實現該接口的抽象方法 alipay(); } void alipay() { System.out.println("使用支付寶支付"); } void wechatPay() { System.out.println("使用微信支付"); } }
public class PayForEat { public static void main(String[] args) { Pay pay = new payImplement(); // 通過接口調用具體實現,實例化的是實現 pay.payOnline(); } |
通過定義接口,可以把A模塊對B模塊的調用,從對具體實現的依賴轉向對抽象接口的依賴。在上述例子中,A模塊可以理解成PayForEat,B模塊可以理解成PayImplement,加入接口Pay,相當於在A模塊和B模塊之間添加了一個第三方橋梁C,A模塊和B模塊的依賴關聯從直接關聯變成了第三方關聯,A通過C依賴於B,即A依賴於C,而C依賴於B。這樣的話,當B模塊發生改變,即上述例子中的支付方式發生改變(比如從使用支付寶支付改變成使用微信支付),A模塊並不需要做任何改動去適應B模塊的改動。
接口和實現分離可以很好地隔離各個模塊,從而盡量降低各個模塊之間的耦合,為系統提供更好的可擴展性和可維護性。
接口體現的是規范和實現分離的設計哲學,讓軟件系統的各個組件之間面向接口耦合,是一種松耦合的設計。
依賴倒置(Dependency Inversion Principle)
依賴倒置原則是建立在抽象接口的基礎上:
A:上層模塊不應該依賴於下層模塊,它們共同依賴於一個抽象。
B:抽象不能依賴於具象,而是具象依賴於抽象。
含義是,為了消除兩個模塊間的依賴關系,應該在兩個模塊之間定義一個抽象接口,上層模塊調用抽象接口定義函數,下層模塊實現該接口。
interface PayInterface { // 定義接口 void payOnline(); // 定義一個抽象方法 } class PayImplement implements PayInterface { // 定義實現 void payOnline() { // 實現該接口的抽象方法 // 調用支付接口 } }
這里定義了一個抽象的接口PayInterface,其中有一個抽象的payOnline()方法,具體的實現在PayImplement實現類中,實現了payOnline()方法。當調用方調用payOnline()方法的時候,由於多態性機制的作用,實際調用的是具體的PayImplement實現類中的實現。因此,抽象接口隔離了調用方和提供方中的具體實現類,使它們之間沒有直接的耦合關系,可以獨立地擴展或重用。
例如我們可以另外定義一個AliPayImplement實現或WepayImplement,應用程序既可以根據需要選擇支付寶支付或微信支付,甚至可以支付寶付一半,微信付一半。由此可以總結出,這種通過抽象接口消除調用方和提供方的實現之間依賴關系的做法具有以下特點:
1.調用方調用提供方的抽象接口,依賴於提供方的抽象接口;具體的實現類派生自提供方的抽象接口,也依賴於提供方的抽象接口。
2.調用方和具體的提供方的實現完全獨立,相互之間沒有直接的依賴關系,只要保持接口類的穩定,調用方和提供方的具體實現都可以獨立地發生改變。
3.提供方完全可以獨立重用,調用方可以和任何一個實現了相同抽象接口的提供方協同工作。
一般情況下,由於提供方的設計者並不知道調用方會如何使用提供的功能,抽象接口大多由提供方設計者根據自己設想的典型使用模式總結出來,並保留一定的靈活度,以提供給調用方的開發者使用。
看到這里就明白,依賴倒置的意思,就是說要程序依賴於抽象接口,而不是依賴於具體實現,把對具體實現的依賴轉移到了抽象接口。簡單的說,就是要求對抽象編程,不要對實現編程,這樣就降低了客戶與實現模塊間的耦合。
控制反轉(Inversion Of Controller)
控制反轉從字面上來看,就是對控制權的反轉。把創建對象的控制權交給第三方容器,當程序中需要用到實例對象的時候,就向第三方容器發出請求,由第三方容器返回一個實例對象。
依賴注入(Dependency Injection)
依賴注入是控制反轉的具體實現。意思就是說當程序中需要用到實例對象的時候,才去向第三方容器發出請求,由第三方容器返回實例對象(注入)。
"忙碌且感恩的人,不會缺愛。"