淺談angular2中的依賴注入

引言

依賴注入,有后端背景的童鞋(尤其是熟悉java spring框架的)應該不會陌生,提到依賴注入,就不得不說一下控制反轉,因為依賴注入實際上是控制反轉概念的一種實現模式。

控制反轉(inversion of control,IoC)原則的非正式稱謂是“好萊塢法則”,它來自好萊塢的一句常用語“別打給我們,我們會打給你(don't call us, we'll call you)”,從控制權的角度來看,控制權從我被反轉到了別人,我從打電話這個行為的發出者,變成了打電話這個行為的接受者。

控制“正”轉可以理解為面向過程編程,定義一個main函數,在main函數里面定義所有的參與者與他們之間的交互過程,一點不能偷懶。需要用到某個對象某個類,自己去new,需要某個庫函數,自己去主動調用。

控制“反”轉可以理解為面向對象編程,我們有了各種適用于不同場景下的框架和設計模式,這些事物幫我們搭建好了一個整體架構,這個架構用于準備一些基本而普遍需要的基礎物資。

控制反轉的設計目標

  • 將執行任務這個動作和任務(具體實現)解耦
  • 讓模塊專注于它自己的設計目標。不需要考慮別的模塊實現了什么,是如何實現的,模塊之間依賴于接口
  • 讓符合相同契約的模塊能夠互相替代

遵循控制反轉的幾個事物

  • 框架:有些太繁瑣太基礎的事情我已經幫你做好了,我做不了的那部分以坑的形式留給你了,填完坑你的工作也就完成了,后邊的事情我來處理
  • 回調、調度器、事件循環:把坑填好,等到時機成熟我就來和你相會
    策略模式:坑的游戲規則我已經定義好(輸入輸出),你只需要按規則填坑就好
  • 模板方式模式:坑的種類和踩坑的順序我已定義好,你只需要負責填坑
  • 工廠模式和依賴注入類似,下面詳細分析

依賴注入

在任何一個有請求作用的系統當中,至少需要有兩個類互相配合工作,在一個入口類下使用new關鍵字創建另一個類的對象實例,“我”充當一個入口類,在這個入口類中,我每次吃飯的時候都要買一雙一次性筷子(每一次使用都要new一次),在這樣的關系下,是“我”(即調用者)每次都要“主動”去買一次性筷子(另一個類),是我控制了筷子,在這種控制正轉的關系下,放在現實生活當中,肯定是不現實的,而且人是懶惰的,他總會去創造出更加方便自己生活的想法,更確切的做法是,買一雙普通的筷子(非一次性),把他放在一個“框架”當中,你需要使用的時候就對“框架”說:我想要用筷子(向框架發出請求),接著筷子就會"注入"到你的手上,而在這個過程當中,你不再是控制方,反而演變成一名請求者(雖然本身還是調用者),依賴于框架給予你資源,控制權落到了框架身上,這就是依賴注入的設計原理。

要理解依賴注入,就必須搞清楚幾個問題:
參與者:都有誰
依賴:誰依賴于誰,為什么需要依賴
注入:誰注入誰,到底注入什么
控制:誰控制誰,控制什么

參與者:
一般有三方參與者,參與者1:某個對象,參與者2:DI框架,參與者3:對象所需的外部資源(類,文件等等)
依賴:
對象依賴于DI框架
為什么需要依賴:
“對象(參與者1)”需要“DI框架(參與者2)”提供所需的“外部資源(參與者3)”
誰注入誰:
DI框架注入“對象(參與者1)”
注入什么:
注入“對象(參與者1)”所需的“外部資源(參與者3)”
誰控制誰:
“DI框架(參與者2)”控制對象(參與者1)”
控制什么:
主要是控制對象所需外部資源實例的創建,管理所需重要對象的生命周期
反轉:
在傳統方式下,也就是正向,如果部件A需要依賴部件B,那就意味著A在內部要創建一個B的實例,也就是A依賴于B。在依賴注入機制也就是反向,如果在部件A中用到部件B,我們就應該期待B被傳給A

正向

反向

依賴注入:它的作用是讓框架幫你處理重要對象的生命周期的管理,不需要你顯式地進行管理(對象構造和銷毀)
優勢:架構松耦合,測試更簡單

Angular 依賴注入

在angular中,依賴注入包括三部分

  • 提供商:負責把一個令牌(可能是字符串也可能是類)映射到一個依賴的列表,它告訴angular該如何根據指定的令牌創建對象
  • 注入器:負責持有一組綁定,當外界要求創建對象時,解析這些依賴并注入它們。我們不需要創建angular注入器,angular在啟動過程中會自動為我們創建一個應用級注入器(platformBrowserDynamic().bootstrapModule(AppModule)
  • 依賴:被用于注入的對象
注入器的提供商
  1. 類提供商 useClass
    providers: [Logger],這其實是注冊提供商的簡寫表達式,完整的應該是providers: [{provide: Logger,useClass: Logger}]
    provide是令牌,用于定位依賴值和注冊提供商
    useClass是提供商,用于定義對象,它用來指出注入什么以及如何注入
    某些時候,我們會請求一個不同的類來提供服務:
    providers: [{provide: Logger,useClass: BetterLogger}]
  2. 別名提供商 useExisting
    制造一個別名來引用以前注冊過的令牌,比如:
    某個舊組件依賴一個OldLogger類,OldLogger和NewLogger具有相同的接口,但是由于某些原因, 我們不能升級這個舊組件并使用它。當舊組件想使用OldLogger記錄消息時,我們希望改用NewLogger的單例對象來記錄,不管組件請求的是新的還是舊的日志服務,依賴注入器注入的都應該是同一個單例對象。 也就是說,OldLogger應該是NewLogger的別名。如果使用useClass應用中會有兩個不同的NewLogger實例,這顯然不是我們想看到的,這時候就可以使用useExisting
providers: [
      NewLogger,
      {provide: OldLogger,useClass: NewLogger} //創建NewLogger的兩個實例
]
      NewLogger,
      {provide: OldLogger,useExisting: NewLogger} //只創建NewLogger的一個實例
]
  1. 值提供商 useValue
    當我們需要一個常量,而它可能會根據應用的其他部分甚至環境進行重定義時,這種方式非常重要。
    官方推薦使用InjectionToken作為令牌
import { InjectionToken } from '@angular/core';
export const TITLE = new InjectionToken <string>('title');
providers:[
       {provide:TITLE, useValue: 'Hero of the Month'}
]
  1. 工廠提供商 useFactory
    使用工廠提供商進行注入,需要寫一個返回任意對象的函數,工廠是創建可諸如對象的最強方式,因為我們可以在工廠函數中“為所欲為”。
providers:[{
        provide: MyComponent,
        useFactory: ()=> {
            if(loggedIn) {
               return new MyloggedComponent();
            }
            return new MyComponent();
        }
}]
依賴注入令牌

  1. 我們最常用的
    providers: [{provide: Logger,useClass: BetterLogger}]
  2. 類-接口
    使用沒有被繼承的抽象類作為依賴注入令牌,這種用法的類叫做:類-接口,它的好處是:提供了接口的強類型,能像正常類一樣把它當做提供商令牌使用。類-接口應該只定義允許它消費者調用的成員,窄的接口有助于解耦該類的具體實現和它的消費者。
    不能使用接口作為依賴注入令牌,因為在JavaScript中并沒有接口的概念,編譯后angular無法識別
    更詳細內容參考angular官方文檔
  3. InjectionToken
    有時候依賴對象并不是一個類,它可能是一個簡單的值,比如日期,數字和字符串,或者一個無形的對象,比如數組和函數。
    這樣的對象沒有應用程序接口,所以不能用一個類來表示,更適合表示它們的是:唯一的和符號性的令牌,一個JavaScript對象,擁有一個友好的名字,但不會與其它的同名令牌發生沖突。
    官方推薦使用InjectionToken實現(其實直接使用字符串也可以,最好還是按照官方推薦方式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或者組件中注冊提供商
通常推薦在對應的模塊(NgModule)中注冊提供商,除非你必須把服務實例的范圍限制到某個組件及其子組件樹

  1. 在NgModule中注冊提供商
@NgModule({
    imports:[],
    declarations:[],
    providers:[ HeroService ]
})

2.在組件中注冊提供商

@Component({
    selector:'my-heros',
    template:`<h2></h2>`
    providers:[ HeroService ]
})
服務

在寫一個服務時必須注意@Injectable() 是必不可少的,除非你不打算注入這個服務,因為@Injectable()相當于C++中的new()

import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
}

如何使用已注冊的服務?在組件或者服務的構造函數中注入即可

constructor(
    public heroService: HeroService 
  ) {}

所有被注入的服務在angular中總是單例的,肯定有童鞋要問了,那我需要多實例的怎么辦?
angular應用程序有多個依賴注入器,組成了一個與組件樹平行的樹狀結構,所以可以在任何組件級別提供和建立服務,當組件申請一個依賴時,angular會從該組件本身的注入器開始,沿著依賴注入器的樹向上查找,所以要實現多實例,在組件內注入服務即可

@host @Optional
有時候我們想限定依賴查找方式,比如只想讓組件向上搜索到宿主組件,可以使用@host,當組件一層層向上查找沒有找到注冊的服務時,就會拋出錯誤,如果不想拋出錯誤,可以使用@Optional,它會把注入參數設置為null

import { Host } from '@angular/core';
constructor( @Host heroService: HeroService ) {}
import { Optional } from '@angular/core';
constructor( @Optional heroService: HeroService ) {}

總結

angularjs中依賴注入涉及的知識面很多,本文只是作了一個簡單介紹,更詳細內容參考官方文檔和API

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 版本:Angular 5.0.0-alpha 依賴注入是重要的應用設計模式。它使用得非常廣泛,以至于幾乎每個人都稱...
    soojade閱讀 3,013評論 0 3
  • 什么是依賴性注入? 依賴性注入( Dependency Injection )其實不是 Angular 獨有的概念...
    接灰的電子產品閱讀 4,084評論 3 18
  • 一、什么是依賴注入 控制反轉(IoC) 控制反轉的概念最早在2004年由Martin Fowler提出,是針對面向...
    Keriy閱讀 3,205評論 0 8
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,915評論 18 139
  • 時間管理,一生都要陪伴的練習,這是第二次看此書,也是第一次寫心得,自己的心情很高興,因為自己開始有點清醒了。 葉武...
    海豚的世界閱讀 2,000評論 3 6