一勉失、什么是依賴注入
控制反轉(zhuǎn) Inversion of Control (簡(jiǎn)稱IOC) 最早在2004年由Martin Fowler提出卵皂,是針對(duì)面向?qū)ο笤O(shè)計(jì)不斷復(fù)雜化而提出的一種設(shè)計(jì)原則,是一種利用面向?qū)ο缶幊谭▌t來降低應(yīng)用程序耦合的設(shè)計(jì)模式。IoC強(qiáng)調(diào)的對(duì)代碼引用的控制權(quán)由調(diào)用方法轉(zhuǎn)移到外部容器沛贪,在運(yùn)行時(shí)通過某種方式注入進(jìn)來,實(shí)現(xiàn)控制的反轉(zhuǎn),這大大降低了服務(wù)類之間的耦合度利赋。依賴注入是一種最常用的實(shí)現(xiàn)IoC的方式水评。
實(shí)現(xiàn)IOC的框架叫IOC容器,Angular實(shí)現(xiàn)控制反轉(zhuǎn)的手段就是依賴注入
依賴注入 Dependency Injection(簡(jiǎn)稱DI)是用來創(chuàng)建對(duì)象及其依賴的其它對(duì)象的一種方式媚送。 當(dāng)依賴注入系統(tǒng)創(chuàng)建某個(gè)對(duì)象實(shí)例時(shí)中燥,會(huì)負(fù)責(zé)提供該對(duì)象所依賴的對(duì)象(稱為該對(duì)象的依賴)。在依賴注入模式中塘偎,應(yīng)用組件無需關(guān)注所依賴對(duì)象的創(chuàng)建和初始化過程疗涉,可以認(rèn)為框架已初始化好了,開發(fā)者只管調(diào)用即可吟秩。依賴注入有利于應(yīng)用程序中各模塊之間的解耦咱扣,使得代碼更容易維護(hù)。這種優(yōu)勢(shì)可能一開始體現(xiàn)不出來涵防,但隨著項(xiàng)目復(fù)雜度的增加闹伪,各模塊、組件壮池、第三方服務(wù)等相互調(diào)用更頻繁時(shí)偏瓤,依賴注入的優(yōu)點(diǎn)就體現(xiàn)出來了。開發(fā)者可以專注于所依賴對(duì)象的消費(fèi)椰憋,無需關(guān)注這些依賴對(duì)象的產(chǎn)生過程厅克,這將大大提升開發(fā)效率。
二橙依、使用依賴注入的好處
1.松耦合和可重用性
2.提高可測(cè)試性
RealLoginService在其他小組開發(fā)完后才進(jìn)行替換已骇,之前的開發(fā)引用MockLoginService的靜態(tài)代碼進(jìn)行測(cè)試。
三票编、Angular中如何實(shí)現(xiàn)依賴注入
1.注入器(Inject):
每一個(gè)組件都有一個(gè)注入器實(shí)例褪储,負(fù)責(zé)注入組件需要的對(duì)象,注入器是Angular提供的一個(gè)服務(wù)類慧域,一般情況下不需要直接調(diào)用注入器的方法鲤竹,注入器會(huì)自動(dòng)通過組件的構(gòu)造函數(shù)將組件所需的對(duì)象注入進(jìn)組件。
constructor(private productService:ProductService){...}
此組件的的構(gòu)造函數(shù)聲明了一個(gè)productService屬性昔榴,他的類型是ProductService
Angular的注入器在看到這樣一個(gè)構(gòu)造函數(shù)聲明時(shí)辛藻,其會(huì)在整個(gè)Angular應(yīng)用中去尋找ProductService的實(shí)例,如果能找到這個(gè)實(shí)例互订,那么就會(huì)將ProductService注入到productService里去吱肌。
2.提供器(Provider):
用于配置注入器,注入器通過它來創(chuàng)建被依賴對(duì)象的實(shí)例仰禽,Provider把標(biāo)識(shí)映射到工廠方法中氮墨,被依賴的對(duì)象就是通過該方法創(chuàng)建的纺蛆。
providers:[ProductService]
等同于 providers:[{provide:ProductService, useClass:ProductService}]
provide指定了提供器的token, useClass表示實(shí)例話屬性為new。
providers:[{provider:ProductService, useClass:AnotherProductService}]
providers:[{provide:ProductService, useFactory:()=>{...}]
3.提供器的作用域規(guī)則
- 當(dāng)一個(gè)提供器聲名在一個(gè)模塊中時(shí)规揪,所有組件都可注入桥氏。
- 當(dāng)一個(gè)提供器聲名在一個(gè)組件時(shí),只對(duì)此組件和其子組件可見猛铅,其他組件不可注入字支。
- 當(dāng)聲明在模塊中的提供器和聲明在組件的提供器具有相同的token時(shí),聲明在組件的提供器會(huì)覆蓋聲明在模塊中的提供器奸忽。
- 一般情況下我們會(huì)優(yōu)先將服務(wù)提供器聲明在模塊中堕伪,只有在服務(wù)必須于某個(gè)組件相對(duì)于其他組件不可見時(shí)才聲明在組件中,而這種情況十分少見栗菜。
四刃跛、在模塊中注入服務(wù)
在根組件中注入這個(gè)服務(wù),所有子組件都能共享這個(gè)服務(wù)苛萎。
在模塊中注入服務(wù)和之前的注入場(chǎng)景稍有不同。Angular在啟動(dòng)程序時(shí)會(huì)啟動(dòng)一個(gè)根模塊检号,并加載它所依賴的其他模塊腌歉,此時(shí)會(huì)生成一個(gè)全局的根注入器,由該注入器創(chuàng)建的依賴注入對(duì)象在整個(gè)應(yīng)用程序級(jí)別可見齐苛,并共享一個(gè)實(shí)例翘盖。同時(shí)根模塊會(huì)指定一個(gè)根組件并啟動(dòng),由該根組件添加的依賴注入對(duì)象是組件樹級(jí)別可見凹蜂,在根組件以及子組件中共享一個(gè)實(shí)例馍驯。
import {NgModule} from '@angular/core'
import {RouterModule} from "@angular/router";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {HttpModule} from "@angular/http";
import {rootRouterConfig} from "./app.routes";
import {AppComponent} from "./app.component";
import {ContactService, UtilService} from "./shared";
@NgModule({
declarations: [
AppComponent
],
imports : [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(rootRouterConfig)],
providers : [ContactService, UtilService],
bootstrap : [AppComponent]
})
export class AppModule {
}
五、層級(jí)注入
Angular以組件為基礎(chǔ)玛痊,項(xiàng)目開發(fā)中自然會(huì)有層級(jí)嵌套的情況汰瘫,這種組織關(guān)系組成了組件樹。根組件下面的各層級(jí)的子組件擂煞,可以出現(xiàn)在任何層級(jí)的任何組件中混弥,每個(gè)組件可以擁有一個(gè)或多個(gè)依賴對(duì)象的注入,每個(gè)依賴對(duì)象對(duì)于注入器而言都是單例对省。
//生成唯一標(biāo)識(shí)服務(wù)
import {Injectable} from "@angular/core";
@Injectable()
export class Random{
public num;
constructor(){
this.num=Math.random();
}
}
//子組件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-a',
providers:[Random],
template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//子組件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-b',
providers:[Random],
template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//父組件
import {Component} from "@angular/core";
@Component({
selector:'contact-list',
template:`
<h1>Contact-List</h1>
<contact-a></contact-a>
<contact-b></contact-b>
`
})
export class ContactListComponent{
constructor(){}
}
結(jié)果將輸出:
Contact-List
ContactA:0.4500488165839276
ContactB:0.5389674473022938
上述的結(jié)果說明蝗拿,每個(gè)子組件都創(chuàng)建了自己獨(dú)立的注入器,也就是說通過依賴注入的Random服務(wù)都是獨(dú)立的蒿涎,如果把注入器提升到父組件中哀托,則結(jié)果將會(huì)不一樣。
//子組件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-a',
//providers:[Random],
template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//子組件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-b',
//providers:[Random],
template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//父組件
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-list',
providers:[Random],
template:`
<h1>Contact-List</h1>
<contact-a></contact-a>
<contact-b></contact-b>
`
})
export class ContactListComponent{
constructor(){}
}
此時(shí)劳秋,結(jié)果變?yōu)?br>
Contact-List
ContactA:0.6257492668005642
ContactB:0.6257492668005642
上述的輸出結(jié)果說明了子組件繼承了父組件的注入器仓手,所以子組件使用了相同的Random實(shí)例胖齐,輸出了相同的結(jié)果。
那么俗或,該如何選擇在根組件還是在子組件中注入服務(wù)呢市怎?
這取決于想讓注入的依賴服務(wù)具有局限性還是全局性,由于每個(gè)注入器總是將它提供的服務(wù)維持單例辛慰,因此区匠,如果不需要針對(duì)每個(gè)組件都提供獨(dú)立的服務(wù)單例,就可以在根組件中注入帅腌,整個(gè)組件樹共享根注入器提供的服務(wù)實(shí)例驰弄;如果需要針對(duì)每個(gè)組件提供不同的服務(wù)實(shí)例,就應(yīng)該在格子組件中配置providers元數(shù)據(jù)來注入服務(wù)速客。
Angular如何查找到合適的服務(wù)實(shí)例呢戚篙?
在組件的構(gòu)造函數(shù)視圖注入某個(gè)服務(wù)的時(shí)候,Angular會(huì)先從當(dāng)前組件的注入器中查找溺职,找不到就繼續(xù)往父組件的注入器查找岔擂,直到根組件注入器,最后到應(yīng)用根注入器浪耘,此時(shí)找不到的話就會(huì)報(bào)錯(cuò)乱灵。
六、使用工廠函數(shù)
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {Product1Component} from './product1/product1.component';
import {ProductService} from './shared/product.service';
import {Product2Component} from './product2/product2.component';
import {LoggerService} from './shared/logger.service';
import {AnotherProductService} from './shared/another-product.service';
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: () => {
let logger = new LoggerService();
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
}
, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule {
}
這樣我們根據(jù)生成隨機(jī)數(shù)的不同而調(diào)用不同的服務(wù)七冲,注意Product1Component和Product2Component兩個(gè)組件使用的服務(wù)永遠(yuǎn)是相同的痛倚,這意味著工廠方法創(chuàng)建的對(duì)象是一個(gè)單例對(duì)像,工廠方法只在創(chuàng)建第一個(gè)需要被注入的對(duì)象時(shí)調(diào)用一次澜躺,然后在整個(gè)應(yīng)用中所有被注入的ProductService的實(shí)例都是同一個(gè)對(duì)象蝉稳。
現(xiàn)在有兩個(gè)問題,一是我們手動(dòng)實(shí)例化ProductService掘鄙,這樣會(huì)造成我們的工廠方法會(huì)跟LoggerService緊密的耦合在一起耘戚,而實(shí)際上我們是有聲明LoggerService這個(gè)提供器的,接下來我們將在工廠方法里使用使用LoggerService提供器操漠。
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService) => {
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService]
}
, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule {
}
使用deps聲明工廠方法需要的參數(shù)毕莱。
第二個(gè)問題,在我們的例子中實(shí)例話哪個(gè)對(duì)象是由一個(gè)隨機(jī)數(shù)決定的颅夺,在真實(shí)情況中我們可能會(huì)通過某個(gè)變量來控制朋截。這時(shí)我們聲明一個(gè)新的提供器
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService, isDev) => {
if (isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService, 'IS_DEV_ENV']
}
, LoggerService,
{
provide: 'IS_DEV_ENV', useValue: false
}],
bootstrap: [AppComponent]
})
export class AppModule {
}
也可以使用值對(duì)象
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService, appConfig) => {
if (appConfig.isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService, 'APP_CONFIG']
}
, LoggerService,
{
provide: 'APP_CONFIG', useValue: {isDev:false}
}],
bootstrap: [AppComponent]
})
export class AppModule {
}
七、在服務(wù)中注入服務(wù)
//logger.service.ts
import {Injectable} from "@angular/core";
@Injectable()
export class LoggerService{
log(message:string){
console.log(message);
}
}
//contact.service.ts
import {Injectable} from "@angular/core";
import {LoggerService} from "./logger.service";
@Injectable()//添加裝飾器@Injectable()
export class ContactService{
//構(gòu)造函數(shù)中注入所依賴服務(wù)
constructor(private _logger:LoggerService){}
getCollections(){
this._logger.log('獲取聯(lián)系人...')
}
}
//在組件的providers元數(shù)據(jù)中注冊(cè)服務(wù)
providers:[LoggerService,ContactService]
- 只有聲明了@Injectable()這個(gè)裝飾器的服務(wù)才可以被注入其他服務(wù)吧黄。
- 組件中沒有@Injectable()也可在構(gòu)造函數(shù)中注入服務(wù)的原因是組件中@Component是@Injectable()的子類部服。