依賴注入( , DI)是 IoC 思想的一種實(shí)現(xiàn),用來解決模塊間的依賴關(guān)系。該設(shè)計(jì)模式的基本思想是集中式的模塊管理,模塊需要的依賴由DI框架統(tǒng)一注入,模塊自身不去主動發(fā)現(xiàn)和構(gòu)建依賴。
長期以來缺乏模塊依賴機(jī)制,近年來隨著前端的變重社區(qū)中出現(xiàn)了非常多的模塊化標(biāo)準(zhǔn)和方案。包括 AMD 標(biāo)準(zhǔn)、 規(guī)范、ES6 等等。而 DI 不僅僅是一種模塊化工具,而是通過控制反轉(zhuǎn)的思想來解決可測試性和復(fù)用性問題。
一個(gè)示例
首先關(guān)注一下引入依賴注入框架前后的代碼有什么區(qū)別。假設(shè)我們有一個(gè),它依賴于一個(gè)對象。
引入依賴注入前,Hard-wired依賴關(guān)系:
function Controller(){
var Router = require('router');
router.redirect('../');
}
引入依賴注入后, :
function Controller(router){
router.redirect('../');
}
上述代碼( )只為闡明DI框架可以對代碼產(chǎn)生怎樣的影響,具體語法和工具仍然取決于DI框架的接口設(shè)計(jì)。
單元測試
引入DI后馬上就能感受到的好處就是單元測試更加容易了。模塊依賴復(fù)雜的代碼很難測試,而硬編碼的依賴使得獨(dú)立的單元測試()非常困難。例如:
function Controller(){
var Router = require('router');
// ...
}
因?yàn)樵趩卧獪y試中無法修改被測的函數(shù)體,它對的依賴是硬編碼的。的缺陷可能會影響你對的單元測試,這違反測試項(xiàng)獨(dú)立的原則。如此一來就很難從單元測試的結(jié)果來追蹤代碼錯(cuò)誤。
當(dāng)然我們可以對進(jìn)行Mock萬能數(shù)據(jù)庫注入插件,但這不是通用方法。試想如果代碼遷移到ES6的時(shí)會怎樣?
依賴注入使得我們可以在單元測試中配置任意的Mock對象,使用DI框架注入給。一個(gè)由于依賴復(fù)雜而不可測試的代碼單元必然是不可復(fù)用的,它只能用于由這些依賴項(xiàng)構(gòu)成的這個(gè)特定環(huán)境。
可配置的依賴
引入依賴注入可以將模塊間依賴暴露出來,可以在不修改源碼的情況下對軟件的結(jié)構(gòu)進(jìn)行配置。這得益于依賴關(guān)系的集中管理,代碼單元不會主動去獲取和構(gòu)造被依賴的對象。
在Java 框架中,這些依賴關(guān)系甚至可以由一個(gè)統(tǒng)一的配置文件來管理。
當(dāng)你有接口一致但實(shí)現(xiàn)各異的模塊時(shí),可配置的依賴非常有用。例如你可能需要在不同的部署版本中使用不同的數(shù)據(jù)庫引擎,但如果源碼中遍布著這樣的代碼:
function queryUser(){

var mongoDB = require('mongodb');
return mongoDB.findAsync('user');
}
替換數(shù)據(jù)庫意味著修改整個(gè)code base。但如果數(shù)據(jù)庫(或數(shù)據(jù)庫)這個(gè)具體的依賴是注入進(jìn)來的:
function queryUser(db){
return db.findAsync('user');
}
只需要更新依賴關(guān)系配置,使用一個(gè)接口一致的來替代即可。
可復(fù)用的模塊
依賴注入使得模塊對自己的具體依賴完全無知。這無疑有助于創(chuàng)建可復(fù)用的代碼。如果一個(gè)代碼單元的所有依賴都由自己去獲取,那么我們說這個(gè)代碼單元與當(dāng)前環(huán)境是硬綁定的。我們就無法在不修改代碼的同時(shí)將它復(fù)用到另一個(gè)環(huán)境中。還是舉古老的『汽車和輪胎』的例子:
// file: car.js
function Car(){
this.tire = require('./track-tire.js')();

}
Car.prototype.start = function(){
this.tire.run();
};
上述代碼中Car是一輛賽車,它自行獲取和構(gòu)建了它的依賴。假如我們希望把創(chuàng)建一輛越野車勢必要重復(fù)上述代碼。如果Car不依賴于具體的實(shí)現(xiàn)而是依賴于Car接口,那么借助DI框架給它注入一個(gè)即可生成一輛越野車。
目前 還沒有接口的概念,但這并不妨礙面向接口的設(shè)計(jì)方式。
如果一個(gè)模塊的行為依賴與其他模塊的實(shí)現(xiàn)(而非接口)萬能數(shù)據(jù)庫注入插件,那么該模塊勢必會難以理解和維護(hù)。所以說依賴注入有助于提高模塊的復(fù)用性,可測試性與可維護(hù)性。
分離的構(gòu)建過程
依賴注入為依賴項(xiàng)提供了構(gòu)建過程與使用過程的分離,這使得應(yīng)用中的模塊更加Clean。正如 C. 比喻的那樣,構(gòu)建和使用是兩個(gè)完全不同的過程。
As I write this, there is a new hotel under that I see out my in . Today it is a bare box with a crane and to the . The busy there all wear hard hats and work . In a year or so the hotel will be . The crane and will be gone. The will be clean, in glass walls and paint. The and there will look a lot too.
對于依賴項(xiàng)而言,構(gòu)建與使用的分離也強(qiáng)制了單一職責(zé)原則(SRP)。在DI框架中存在的概念來提供復(fù)雜的構(gòu)建過程,例如下面來自 1的示例:
myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
var useTinfoilShielding = false;

this.useTinfoilShielding = function(value) {
useTinfoilShielding = !!value;
};
this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {
// let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding);
}];
});
To turn the tinfoil shielding on in our app, we need to create a config function via the module API and have the UnicornLauncherProvider injected into it:
myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
unicornLauncherProvider.useTinfoilShielding(true);

}]);
中,的配置是在應(yīng)用生命周期的階段完成的。此后run階段中,實(shí)例才被創(chuàng)建和使用。
可能你不熟悉前端MVC,但如果你在Node.js下工作也一定會遇到類似的場景:需要在一個(gè)流程入口(或統(tǒng)一的配置中心)進(jìn)行一些初始化或構(gòu)建工作,但很難保證這些初始化操作在其他代碼單元?jiǎng)?chuàng)建之前執(zhí)行。
這就是因?yàn)橹皇墙鉀Q了模塊化問題,并不像DI框架那樣提供控制反轉(zhuǎn),當(dāng)然也沒有配置階段來讓你執(zhí)行這些操作。
AMD 是依賴注入嗎?
AMD 不是依賴注入。DI的關(guān)鍵是模塊只負(fù)責(zé)依賴的使用而不去主動查找或構(gòu)建依賴。但 AMD 與 DI 也有相似之處,它們都有獨(dú)立的依賴解決工具負(fù)責(zé)加載模塊依賴。不同的是 AMD 還提供了異步模塊加載的功能,而 DI 分離了依賴的創(chuàng)建和使用。
支持并行的開發(fā)
由于每個(gè)代碼單元對外部依賴完全無知,所以依賴注入能夠很好地支持并行開發(fā)。開發(fā)者無需依賴于對方產(chǎn)出的代碼單元,只需知道對方的接口。你可能會想到如何運(yùn)行自己的程序并驗(yàn)證是否正確工作呢?想想單元測試的/原則,驗(yàn)證你的模塊正確工作不需要依賴于其他模塊。
目前上非常流行的插件式架構(gòu)正是依賴注入的思想,插件往往由第三方并行地開發(fā)而很少需要交流。其實(shí)也有部分插件式架構(gòu)屬于策略模式,使用組合和代理實(shí)現(xiàn)。比如.js有近300位插件開發(fā)者,的引擎中和Tag也采用插件式架構(gòu)。
支持平滑重構(gòu)
依賴注入是一種對重構(gòu)非常友好的設(shè)計(jì)模式,你可以在不改變原有代碼行為的條件下遷移到依賴注入框架。同時(shí)遷移后的代碼也更容易進(jìn)行獨(dú)立的單元測試。這些好處對于遷移遺產(chǎn)代碼而言非常重要。
平滑過渡,和平解放。
哪些場景不適合引入DI?
不同于編程風(fēng)格和設(shè)計(jì)哲學(xué),軟件設(shè)計(jì)模式的優(yōu)缺點(diǎn)和適用性是有普遍共識的。其中DI也不是萬能的,只能解決一類特定的問題。過度設(shè)計(jì)與缺乏設(shè)計(jì)一樣罪惡,不要沉溺于任何一種自己熟悉的設(shè)計(jì)模式。
關(guān)于Unix哲學(xué)和Vim信仰,卻是不可動搖的。
這些場景下,引入DI并不合適: