摘要:上面這部分代碼不變,還是通過在構造器中傳入依賴的方式初始化依賴調用這里,調用方無需了解內部對的依賴。而配置一般用于上自動掃描并注入的代碼如下這里只給出直接在依賴對象上添加注解的形式,還可以通過構造器和注入依賴,這里就不多說了。
前言
相信所有面試java開發的童鞋一定都被問到過是否使用過Spring,是否了解其IOC容器,為什么不直接使用工廠模式,以及究竟IOC和DI區別在于哪里這種問題。今天就結合JAVA語言,解釋一下究竟是如何衍生出DI模式,以及其在Spring中的實現。
很久很久以前初學Java,我們一定會學到面向對象的編程思想,以及使用new關鍵字新建一個對象。假設現在有一個郵件發送系統,該系統包含拼寫檢查功能。那么本著面向對象的思想以及關注點分離的思想,我們會將其分解為兩個類:Emailer和SpellChecker。其中,Emailer依賴著SpellChecker提供的服務,這兩個類的實現如下:
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(){ spellChecker = new SpellChecker(); } }
可以看到我們在構造器中使用new新建了一個SpellChecker的對象。
現在我們來分析一下這個實現的不足之處:
可測試性:假設現在我希望測試Emailer的功能是否完善,但是此時SpellChecker并沒有完成開發與測試,那么我們將無法對Emailer進行測試。就算SpellChecker已經開發完成,但是我們也無法排除當前的錯誤是否和SpellChecker的實現無關。
可維護性:假設現在支持多語種,那么我需要分別實現一個EnglishEmailer和FrenchEmailer類。他們的構造函數中分別初始化EnglishSpellChecker和FrenchSpellChecker。以后每增加一個語種都需要新建一個新的Emailer類。而這些類的代碼本質上都是重復的。更不要提假設里面
因此我們就需要一種新的初始化依賴的方式。
自己初始化不行,那你給我一個現成的吧!既然在調用依賴的類中初始化依賴這么麻煩,不如將構建完成的依賴傳入調用的類。
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(SpellChecker spellChecker){ this.spellChecker = spellChecker; } } //使用 Emailer email = new Emailer(new EnglishSpellChecker())
從測試性的角度來說,這個代碼明顯更加易于測試了,我們可以提供SpellChecker的一個Mock實現,如Emailer e = new Emailer(new MockSpellChecker())來對Emailer進行測試。那么這種實現的缺點在哪里呢?
首先,調用Emailer的代碼需要知道如何去初始化SpellChecker,而這明顯暴露了Emailer的內部實現,違背了信息隱藏的思想。其次,一旦依賴發生變化,比如Emailer還需要依賴一個定時裝置Scheduler來實現定時發送郵件,那么所有的調用Emailer的代碼都需要發生改變。顯然,這種寫法的可維護性依然不高。
工廠模式閃亮登場,所有的初始化都交給我了!那么,我們是否可以將所有對象構建的代碼提取出來,像工廠標準件一樣生產出來。所有對對象的調用都通過工廠提供。
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(SpellChecker spellChecker){ this.spellChecker = spellChecker; } } //上面這部分代碼不變,還是通過在構造器中傳入依賴的方式初始化依賴 public class EmailerFactory { public Emailer newFrenchEmailer(){ return new Emailer(new FrenchSpellChecker()); } } //調用 Emailer email = new EmailerFactory().newFrenchEmailer();
這里,調用方無需了解內部對SpellChecker的依賴。無論之后Emailer的依賴發生什么樣的變化,客戶端代碼都不會受到影響。那么這種設計有沒有缺陷呢?
當然是有的。Emailer的測試和之前一樣,我們可以通過傳入Mock的對象來對其進行測試。那么調用Emailer的服務怎么辦呀?在調用方看來我們只是依賴著Factory對象,因此我們需要通過定義Factory返回一個Mock對象才行,同時這個對象還不能影響真正的Factory的實現。
除此以外,每當我們對一個新的語種添加支持時,我們都必須添加一段新的代碼,如下:
public class EmailerFactory { public Emailer newJapaneseEmailer() { Emailer service = new Emailer(); service.setSpellChecker(new JapaneseSpellChecker()); return service; } public Emailer newFrenchEmailer() { Emailer service = new Emailer(); service.setSpellChecker(new FrenchSpellChecker()); return service; } }
而這兩段初始化代碼基本上是完全相同的!而假設以后我們需要實現一個全球通用版本。。。
光是無聊的工廠模式代碼就要花費我們大量的時間!
有沒有這樣一個東西,客戶端代碼報出它的編號key,它就會返回那個對象的實例。當然這個實例是根據配置生成的。比如Emailer English這樣的key,就會返回英語的Emailer。這種思路衍生出了服務定位模式。這個模式相當于站在了所有工廠模式的最前端。它就像是一個老式的電話中轉服務,調用服務的人輸入服務的唯一編號,即電話號碼,而服務定位器找到該服務并返回該服務的實例。調用如下:
Emailer emailer = (Emailer) new ServiceLocator().get("Emailer");
JNDI(Java Naming and Directory Interface)就是該思想下的一個實現。服務的提供方在JNDI上注冊服務,之后調用方在JNDI上檢索服務,實現二者之間的解耦。
這個模式的問題和工廠模式類似,難以測試以及需要管理共享狀態。其次,通過使用String類型的Key來獲取服務無法在編譯時對服務調用是否正確以及服務類型是否正確進行檢查。
這里將不會給出JNDI的具體實現,對JNDI的概念有困惑的可以查看這篇文章
Injector隆重登場看來,任何和構造對象相關的代碼夾雜在業務代碼中都會帶來麻煩,那么我們可以將這部分代碼全權委托給構造框架,業務代碼通過依賴注入從而關注于業務本身,而框架可以通過配置甚至是自動的生成對象注入到客戶端。從而實現二者的完全解耦。
至此,對象關聯圖的構造,聯系和組裝將和業務代碼完全無關,這種情況也被成為控制反轉(IOC)
不同的框架對于依賴注入的實現是不同的,但是本質上來說,他們都確保了客戶端無需在業務代碼中了解注入的依賴是如何初始化的。
IOC vs DI那么IOC和DI之間的區別究竟是什么呢?
IOC這個概念所表示的領域其實超出了依賴注入的范圍,它更多強調的是控制反轉,也就是說,這個對象是別人替你創建好的。因此DI是IOC的一種實現機制。而控制反轉可以運用于更多的場景,如:
J2EE應用服務器中的一個模塊,比如Servlet
框架自動調用的測試方法
點擊鼠標后調用的事件處理器
IOC不僅負責創建對象,還需要管理對象的生命周期。不同的生命周期需要觸發不同的調用,這些調用被稱為回調函數。除此以外,IOC容器管理的對象需要被打上標記,比如使用@Autowire,@Component注解的類和對象,以及繼承了Servlet接口的Servlet才會被Servlet容器管理。
因此我們常見的Spring更像是將IOC和DI思想結合在一起生成的產物。
更多關于IOC VS DI可以參考這篇文章
SpringSpring是一個輕量級的依賴注入框架,它已經成了所有JAVA開發者無法躲開的開發大禮包。Spring提供了三種依賴注入的方式:XML,注解和Java Config
XML方式曾經非常流行,但是這種方式也逐漸暴露出問題,主要的問題在于無法對注入的依賴進行類型檢查,從而導致代碼無法在編譯期間識別出問題,只能在運行期間拋出異常?,F在主要推薦自動掃描并注入以及通過JavaConfig代碼來配置。而XML配置一般用于Legacy System上
Autowired自動掃描并注入的代碼如下:
public class Emailer{ @Autowired private SpellChecker spellChecker; public class Emailer(SpellChecker spellChecker){ } } @Component public class SpellChecker{ ... } @ComponentScan public class EmailerConfig{ }
這里只給出直接在依賴對象上添加注解的形式,還可以通過構造器和setter注入依賴,這里就不多說了。
Java ConfigJava Config則是將配置代碼多帶帶提取出來:
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(){ return new Emailer(new EnglishSpellChecker()); } }
當然,這里也可以通過依賴注入的方式來確保傳入的對象是單例的(默認情況下Spring生成的對象為單例)
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(SpellChecker spellChecker){ return new Emailer(spellChecker); } }
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注我的微信公眾號!將會不定期的發放福利哦~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69658.html
摘要:引述最近看設計模式以及代碼,對于控制反轉以及依賴注入這些概念非常困惑,于是找了一些資料,以下是對于控制反轉的一下理解。其中最常見的方式叫做依賴注入,簡稱,還有一種方式叫依賴查找。在軟件工程中,依賴注入是種實現控制反轉用于解決依賴性設計模式。 引述 最近看設計模式以及laravel代碼,對于控制反轉以及依賴注入這些概念非常困惑,于是找了一些資料,以下是對于控制反轉的一下理解。 概念 Io...
摘要:劃下重點,服務容器是用于管理類的依賴和執行依賴注入的工具。類的實例化及其依賴的注入,完全由服務容器自動的去完成。 本文首發于 深入剖析 Laravel 服務容器,轉載請注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。 之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請求,又是如何生成響應并最終呈現給用戶的工作原理。 本章將帶領大...
摘要:然而,我們需要注意的是僅是軟件設計模式依賴注入的一種便利的實現形式。容器本身不是依賴注入的必要條件,在框架他只是讓其變得更加簡便。首先,讓我們探索下為什么依賴注入是有益的。繼續深入讓我們通過另一個示例來加深對依賴注入的理解。 聲明:本文并非博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味的翻譯,能保證9...
摘要:本文一大半內容都是通過舉例來讓讀者去理解什么是控制反轉和依賴注入,通過理解這些概念,來更加深入。這種由外部負責其依賴需求的行為,我們可以稱其為控制反轉。工廠模式,依賴轉移當然,實現控制反轉的方法有幾種。 容器,字面上理解就是裝東西的東西。常見的變量、對象屬性等都可以算是容器。一個容器能夠裝什么,全部取決于你對該容器的定義。當然,有這樣一種容器,它存放的不是文本、數值,而是對象、對象的描...
摘要:控制反轉容器控制反轉使依賴注入變得更加便捷。有瑕疵控制反轉容器是實現的控制翻轉容器的一種替代方案。容器的獨立使用即使沒有使用框架,我們仍然可以在項目中使用安裝組件來使用的控制反轉容器。在沒有給定任何信息的情況下,容器是無法實例化相關依賴的。 聲明:本文并非博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味...
閱讀 769·2021-11-23 09:51
閱讀 835·2021-11-23 09:51
閱讀 2503·2021-11-15 18:01
閱讀 3862·2021-10-11 11:07
閱讀 2396·2021-09-22 15:30
閱讀 1075·2021-09-22 14:59
閱讀 1557·2019-08-30 15:55
閱讀 1753·2019-08-30 15:52