引言
依賴注入恰响,有后端背景的童鞋(尤其是熟悉java spring框架的)應(yīng)該不會陌生,提到依賴注入沼撕,就不得不說一下控制反轉(zhuǎn)侧啼,因為依賴注入實際上是控制反轉(zhuǎn)概念的一種實現(xiàn)模式。
控制反轉(zhuǎn)(inversion of control泪蔫,IoC)原則的非正式稱謂是“好萊塢法則”棒旗,它來自好萊塢的一句常用語“別打給我們,我們會打給你(don't call us, we'll call you)”撩荣,從控制權(quán)的角度來看铣揉,控制權(quán)從我被反轉(zhuǎn)到了別人,我從打電話這個行為的發(fā)出者餐曹,變成了打電話這個行為的接受者逛拱。
控制“正”轉(zhuǎn)可以理解為面向過程編程,定義一個main函數(shù)台猴,在main函數(shù)里面定義所有的參與者與他們之間的交互過程朽合,一點不能偷懶。需要用到某個對象某個類饱狂,自己去new旁舰,需要某個庫函數(shù),自己去主動調(diào)用嗡官。
控制“反”轉(zhuǎn)可以理解為面向?qū)ο缶幊碳埽覀冇辛烁鞣N適用于不同場景下的框架和設(shè)計模式,這些事物幫我們搭建好了一個整體架構(gòu)衍腥,這個架構(gòu)用于準備一些基本而普遍需要的基礎(chǔ)物資磺樱。
控制反轉(zhuǎn)的設(shè)計目標
- 將執(zhí)行任務(wù)這個動作和任務(wù)(具體實現(xiàn))解耦
- 讓模塊專注于它自己的設(shè)計目標。不需要考慮別的模塊實現(xiàn)了什么婆咸,是如何實現(xiàn)的竹捉,模塊之間依賴于接口
- 讓符合相同契約的模塊能夠互相替代
遵循控制反轉(zhuǎn)的幾個事物
- 框架:有些太繁瑣太基礎(chǔ)的事情我已經(jīng)幫你做好了,我做不了的那部分以坑的形式留給你了尚骄,填完坑你的工作也就完成了块差,后邊的事情我來處理
- 回調(diào)、調(diào)度器倔丈、事件循環(huán):把坑填好憨闰,等到時機成熟我就來和你相會
策略模式:坑的游戲規(guī)則我已經(jīng)定義好(輸入輸出),你只需要按規(guī)則填坑就好 - 模板方式模式:坑的種類和踩坑的順序我已定義好需五,你只需要負責(zé)填坑
- 工廠模式和依賴注入類似鹉动,下面詳細分析
依賴注入
在任何一個有請求作用的系統(tǒng)當(dāng)中,至少需要有兩個類互相配合工作宏邮,在一個入口類下使用new關(guān)鍵字創(chuàng)建另一個類的對象實例泽示,“我”充當(dāng)一個入口類缸血,在這個入口類中,我每次吃飯的時候都要買一雙一次性筷子(每一次使用都要new一次)械筛,在這樣的關(guān)系下捎泻,是“我”(即調(diào)用者)每次都要“主動”去買一次性筷子(另一個類),是我控制了筷子埋哟,在這種控制正轉(zhuǎn)的關(guān)系下族扰,放在現(xiàn)實生活當(dāng)中,肯定是不現(xiàn)實的定欧,而且人是懶惰的,他總會去創(chuàng)造出更加方便自己生活的想法怒竿,更確切的做法是砍鸠,買一雙普通的筷子(非一次性),把他放在一個“框架”當(dāng)中耕驰,你需要使用的時候就對“框架”說:我想要用筷子(向框架發(fā)出請求)爷辱,接著筷子就會"注入"到你的手上,而在這個過程當(dāng)中朦肘,你不再是控制方饭弓,反而演變成一名請求者(雖然本身還是調(diào)用者),依賴于框架給予你資源媒抠,控制權(quán)落到了框架身上弟断,這就是依賴注入的設(shè)計原理。
要理解依賴注入趴生,就必須搞清楚幾個問題:
參與者:都有誰
依賴:誰依賴于誰阀趴,為什么需要依賴
注入:誰注入誰,到底注入什么
控制:誰控制誰苍匆,控制什么
參與者:
一般有三方參與者刘急,參與者1:某個對象,參與者2:DI框架浸踩,參與者3:對象所需的外部資源(類叔汁,文件等等)
依賴:
對象依賴于DI框架
為什么需要依賴:
“對象(參與者1)”需要“DI框架(參與者2)”提供所需的“外部資源(參與者3)”
誰注入誰:
DI框架注入“對象(參與者1)”
注入什么:
注入“對象(參與者1)”所需的“外部資源(參與者3)”
誰控制誰:
“DI框架(參與者2)”控制對象(參與者1)”
控制什么:
主要是控制對象所需外部資源實例的創(chuàng)建,管理所需重要對象的生命周期
反轉(zhuǎn):
在傳統(tǒng)方式下检碗,也就是正向据块,如果部件A需要依賴部件B,那就意味著A在內(nèi)部要創(chuàng)建一個B的實例折剃,也就是A依賴于B瑰钮。在依賴注入機制也就是反向,如果在部件A中用到部件B微驶,我們就應(yīng)該期待B被傳給A
依賴注入:它的作用是讓框架幫你處理重要對象的生命周期的管理浪谴,不需要你顯式地進行管理(對象構(gòu)造和銷毀)
優(yōu)勢:架構(gòu)松耦合开睡,測試更簡單
Angular 依賴注入
在angular中,依賴注入包括三部分
- 提供商:負責(zé)把一個令牌(可能是字符串也可能是類)映射到一個依賴的列表苟耻,它告訴angular該如何根據(jù)指定的令牌創(chuàng)建對象
- 注入器:負責(zé)持有一組綁定篇恒,當(dāng)外界要求創(chuàng)建對象時,解析這些依賴并注入它們凶杖。我們不需要創(chuàng)建angular注入器胁艰,angular在啟動過程中會自動為我們創(chuàng)建一個應(yīng)用級注入器(
platformBrowserDynamic().bootstrapModule(AppModule)
) - 依賴:被用于注入的對象
注入器的提供商
- 類提供商 useClass
providers: [Logger],這其實是注冊提供商的簡寫表達式智蝠,完整的應(yīng)該是providers: [{provide: Logger,useClass: Logger}]
provide是令牌腾么,用于定位依賴值和注冊提供商
useClass是提供商,用于定義對象杈湾,它用來指出注入什么以及如何注入
某些時候解虱,我們會請求一個不同的類來提供服務(wù):
providers: [{provide: Logger,useClass: BetterLogger}]
- 別名提供商 useExisting
制造一個別名來引用以前注冊過的令牌,比如:
某個舊組件依賴一個OldLogger類漆撞,OldLogger和NewLogger具有相同的接口殴泰,但是由于某些原因, 我們不能升級這個舊組件并使用它浮驳。當(dāng)舊組件想使用OldLogger記錄消息時悍汛,我們希望改用NewLogger的單例對象來記錄,不管組件請求的是新的還是舊的日志服務(wù)至会,依賴注入器注入的都應(yīng)該是同一個單例對象离咐。 也就是說,OldLogger應(yīng)該是NewLogger的別名奉件。如果使用useClass應(yīng)用中會有兩個不同的NewLogger實例健霹,這顯然不是我們想看到的,這時候就可以使用useExisting
providers: [
NewLogger,
{provide: OldLogger,useClass: NewLogger} //創(chuàng)建NewLogger的兩個實例
]
NewLogger,
{provide: OldLogger,useExisting: NewLogger} //只創(chuàng)建NewLogger的一個實例
]
- 值提供商 useValue
當(dāng)我們需要一個常量瓶蚂,而它可能會根據(jù)應(yīng)用的其他部分甚至環(huán)境進行重定義時糖埋,這種方式非常重要。
官方推薦使用InjectionToken作為令牌
import { InjectionToken } from '@angular/core';
export const TITLE = new InjectionToken <string>('title');
providers:[
{provide:TITLE, useValue: 'Hero of the Month'}
]
- 工廠提供商 useFactory
使用工廠提供商進行注入窃这,需要寫一個返回任意對象的函數(shù)瞳别,工廠是創(chuàng)建可諸如對象的最強方式,因為我們可以在工廠函數(shù)中“為所欲為”杭攻。
providers:[{
provide: MyComponent,
useFactory: ()=> {
if(loggedIn) {
return new MyloggedComponent();
}
return new MyComponent();
}
}]
依賴注入令牌
- 類
我們最常用的
providers: [{provide: Logger,useClass: BetterLogger}]
- 類-接口
使用沒有被繼承的抽象類作為依賴注入令牌祟敛,這種用法的類叫做:類-接口,它的好處是:提供了接口的強類型兆解,能像正常類一樣把它當(dāng)做提供商令牌使用馆铁。類-接口應(yīng)該只定義允許它消費者調(diào)用的成員,窄的接口有助于解耦該類的具體實現(xiàn)和它的消費者锅睛。
不能使用接口作為依賴注入令牌埠巨,因為在JavaScript中并沒有接口的概念历谍,編譯后angular無法識別
更詳細內(nèi)容參考angular官方文檔 - InjectionToken
有時候依賴對象并不是一個類,它可能是一個簡單的值辣垒,比如日期望侈,數(shù)字和字符串,或者一個無形的對象勋桶,比如數(shù)組和函數(shù)脱衙。
這樣的對象沒有應(yīng)用程序接口,所以不能用一個類來表示例驹,更適合表示它們的是:唯一的和符號性的令牌捐韩,一個JavaScript對象,擁有一個友好的名字鹃锈,但不會與其它的同名令牌發(fā)生沖突荤胁。
官方推薦使用InjectionToken實現(xiàn)(其實直接使用字符串也可以,最好還是按照官方推薦方式O(∩_∩)O)
import { InjectionToken } from '@angular/core';
export const TITLE = new InjectionToken <string>('title');
export const RUNNERS_UP= new InjectionToken <string>('RunnersUP');
providers:[
{provide:TITLE, useValue: 'Hero of the Month'},
{RUNNERS_UP, useFactory: runnersUpFactory(2),dep[Hero, HeroService]}
]
注冊提供商
可以在NgModule或者組件中注冊提供商
通常推薦在對應(yīng)的模塊(NgModule)中注冊提供商仪召,除非你必須把服務(wù)實例的范圍限制到某個組件及其子組件樹
- 在NgModule中注冊提供商
@NgModule({
imports:[],
declarations:[],
providers:[ HeroService ]
})
2.在組件中注冊提供商
@Component({
selector:'my-heros',
template:`<h2></h2>`
providers:[ HeroService ]
})
服務(wù)
在寫一個服務(wù)時必須注意@Injectable() 是必不可少的,除非你不打算注入這個服務(wù)松蒜,因為@Injectable()相當(dāng)于C++中的new()
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
}
如何使用已注冊的服務(wù)?在組件或者服務(wù)的構(gòu)造函數(shù)中注入即可
constructor(
public heroService: HeroService
) {}
所有被注入的服務(wù)在angular中總是單例的扔茅,肯定有童鞋要問了,那我需要多實例的怎么辦秸苗?
angular應(yīng)用程序有多個依賴注入器召娜,組成了一個與組件樹平行的樹狀結(jié)構(gòu),所以可以在任何組件級別提供和建立服務(wù)惊楼,當(dāng)組件申請一個依賴時玖瘸,angular會從該組件本身的注入器開始,沿著依賴注入器的樹向上查找檀咙,所以要實現(xiàn)多實例雅倒,在組件內(nèi)注入服務(wù)即可
@host @Optional
有時候我們想限定依賴查找方式,比如只想讓組件向上搜索到宿主組件弧可,可以使用@host蔑匣,當(dāng)組件一層層向上查找沒有找到注冊的服務(wù)時,就會拋出錯誤棕诵,如果不想拋出錯誤裁良,可以使用@Optional,它會把注入?yún)?shù)設(shè)置為null
import { Host } from '@angular/core';
constructor( @Host heroService: HeroService ) {}
import { Optional } from '@angular/core';
constructor( @Optional heroService: HeroService ) {}
總結(jié)
angularjs中依賴注入涉及的知識面很多校套,本文只是作了一個簡單介紹价脾,更詳細內(nèi)容參考官方文檔和API