概念
依賴注入是一種設計思想,並不是某一類語言所特有的,因此可以參考開濤大神關於學習Java語言的Spring框架
時對其的解釋:
DI—Dependency Injection,即“依賴注入”:是組件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的並非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,並為系統搭建一個靈活、可擴展的平台。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:“誰依賴誰,為什么需要依賴,誰注入誰,注入了什么”,那我們來深入分析一下:
- 誰依賴於誰:當然是應用程序依賴於IoC容器;
- 為什么需要依賴:應用程序需要IoC容器來提供對象需要的外部資源;
- 誰注入誰:很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象;
- 注入了什么:就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)。
IoC和DI由什么關系呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關系),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入對象依賴IoC容器配置依賴對象”。 [1]
Angular中的依賴注入
Angular中可以在app.module.ts
中進行全局聲明,也可以在組件內部根據特定需求實現特定聲明。
聲明是通過屬性providers
實現的,顧名思義,它是一個對象提供者,復數形式表名其為數組形式。且子類型為Provide
,它位於@angular/core
中,實現如下:
export interface TypeProvider extends Type<any> {
}
export interface ValueProvider {
provide: any;
useValue: any;
multi?: boolean;
}
export interface ClassProvider {
provide: any;
useClass: Type<any>;
multi?: boolean;
}
export interface ExistingProvider {
provide: any;
useExisting: any;
multi?: boolean;
}
export interface FactoryProvider {
provide: any;
useFactory: Function;
deps?: any[];
multi?: boolean;
}
export declare type Provider =
TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];
可以看到,Provider
共有5個明確的子類,也決定了其使用方式的數量。由於全局聲明和組件內部聲明的方式差別不大,可見的區別是在app.module.ts
中屬性providers
是在模塊注解@NgModule
中聲明而組件中是通過注解@Component
聲明。因此實現方式不對全局和組件加以區分。
由於是初學,此處僅列舉常用的幾種方式:useClass
、useFactory
和useValue
。
useClass
providers: [{provide: DefaultBookService, useClass: DefaultBookService}]
其中provide
定義了token
,而useClass
則指明了使用的類,此處由於token
和實現類相同,均為DefaultBookService
因此可以簡寫為providers: [DefaultBookService]
。
useFactory
providers: [{
provide: DefaultBookService, useFactory: () => {
const rdm = Math.random() < 0.5;
if (rdm) {
return new DefaultBookService();
} else {
return new HuaZhangBookService();
}
}
HuaZhangBookService
是DefaulteBookService
的一個子類。根據情況返回所需注入的對象。
如果通過依賴注入的對象同時依賴另一個通過依賴注入的對象應如何實現呢?比如DefaultBookService
,它的構造函數為:
constructor(public logger: LoggerService) { }
,而LoggerService也是通過依賴注入-即不需要自己手工new LoggerService()
-時應該怎么辦?在類FactoryProvider
中提供了一個名叫deps
的參數,它可以幫助我們實現依賴對象的引用。用法如下:
providers: [{
provide: DefaultBookService, useFactory: (logger: LoggerService) => {
const rdm = Math.random() < 0.5;
if (rdm) {
return new DefaultBookService(logger);
} else {
return new HuaZhangBookService(logger);
}
}, deps: [LoggerService]
}, LoggerService]
deps
是一個任意類型的數組,為什么呢,因為類的參數不會只有一個,當構造方法中有多個參數時,可同時依賴多個不同類型的對象。
useValue
基本用法:
{provide: 'IS_DEV_ENV', useValue: Math.random() < 0.5}
擴展用法:
providers: [{
provide: DefaultBookService, useFactory: (logger: LoggerService, isDev) => {
if (isDev) {
return new DefaultBookService(logger);
} else {
return new HuaZhangBookService(logger);
}
}, deps: [LoggerService, 'IS_DEV_ENV']
},
LoggerService, {provide: 'IS_DEV_ENV', useValue: Math.random() < 0.5}
]
可以看到,在useFactory
的箭頭表達式中有兩個參數,他們依次對應deps
中的token
,即isDev對應token為IS_DEV_ENV
的對象,而它正是使用了本小節中所涉及的useValue
形式,它返回一個boolean
對象。
另外需要注意的是,token
不止能指定特定的類,也可以指定特定的值。
multi
參考
其他
- 以上我們簡單介紹了幾種Angular實現依賴注入的方式,但是還需要提供使用它的條件,在Angular中,只有當組件或者服務被注解
@Injectable
或其子類如@Component
修飾后,才可使用,建議無論是否使用依賴注入,都使用@Injectable
修飾。 - 可以通過構造方法的參數來放置通過依賴生成的對象:
constructor(private bookService: DefaultBookService) { }
,此時,其他方法中直接使用this.bookService
來調用DefaultBookService
中的方法了。 - 通過依賴注入傳入的對象,可以通過
Injector
來進行訪問,它位於@angular/core
包中,因此上面的構造方法可以這樣聲明constructor(private injector: Injector) { }
,通過this.injector.get(DefaultBookService)
來獲取DefaultBookService
對象並調用其中的方法,但不建議這樣做,因為不如直接傳入所需對象來的明確。 - 在
app.module.ts
中使用useFactory
並且使用箭頭表達式來實現時,注冊的組件會報錯,至今無解,相關問題已發至segmentFault,哪位大神如果知道,煩請告訴,跪謝。 - 關於
Token
的學習可以參考Angular 4 依賴注入教程之八 InjectToken的使用
如果想要更加深入的了解IoC和DI,請參考大師級人物Martin Fowler的一篇經典文章《Inversion of Control Containers and the Dependency Injection pattern》,原文地址:http://www.martinfowler.com/articles/injection.html。 ↩︎