发表于: 2019-02-18 19:44:47

1 496


今天完成的事情:

Angular 依赖注入简介

 angular angular 2018/08/11

在介绍依赖注入的概念和作用前,我们先来看个例子。各位同学请睁大眼睛,我要开始 “闭门造车” 了。

一辆车内部构造很复杂,出于简单考虑,我们就只考虑三个部分:车身、车门和引擎。

现在我们来分别定义各个部分:

定义车身类

export default class Body { }

定义车门类

export default class Doors { }

定义车引擎类

export default class Engine {

  start() {

    console.log('开动鸟~~~');

  }

}

定义汽车类

import Engine from './engine';

import Doors from './doors';

import Body from './body';

export default class Car {

    engine: Engine;

    doors: Doors;

    body: Body;

    constructor() {

      this.engine = new Engine();

      this.body = new Body();

      this.doors = new Doors();

    }

    run() {

      this.engine.start();

    }

}

一切已准备就绪,我们马上来造一辆车:

let car = new Car(); // 造辆新车

car.run(); // 开车上路咯

车已经可以成功上路,但却存在以下问题:

问题一:在创建新车的时候,你没有选择,假设你想更换汽车引擎的话,按照目前的方案,是实现不了的。

问题二:在汽车类内部,你需要在构造函数中手动去创建各个部件。

为了解决第一个问题,提供更灵活的方案,我们需要重构一下 Car 类:

export default class Car {

    engine: Engine;

    doors: Doors;

    body: Body;

    constructor(engine, body, doors) {

      this.engine = engine;

      this.body = body;

      this.doors = doors;

    }

    run() {

      this.engine.start();

    }

}

重构完 Car 类,我们来重新造辆新车:

let engine = new NewEngine();

let body = new Body();

let doors = new Doors();

this.car = new Car(engine, body, doors);

this.car.run();

此时我们已经解决了上面提到的第一个问题,要解决第二个问题我们要先介绍一下依赖注入的概念。

依赖注入的概念

在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。 传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。 —— 维基百科

控制反转 (inversion of control,IoC) 原则的非正式称谓是“好莱坞法则”。它来自好莱坞的一句常用语“别打给我们,我们会打给你 (don’t call us, we’ll call you)”。

一般情况下,如果服务 A 需要服务 B,那就意味着服务 A 要在内部创建服务 B 的实例,也就说服务 A 依赖于服务 B:

no-di

(图片来源 —— Angular 权威指南)

Angular 利用依赖注入机制改变了这一点,在该机制下,如果服务 A 中需要服务 B,即服务 A 依赖于服务 B,那么我们期望服务 B 能被自动注入到服务 A 中,如下图所示:

di

(图片来源 —— Angular 权威指南)

在 Angular 中,依赖注入包括以下三个部分:

提供者负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表。它告诉 Angular 该如何根据指定的令牌创建对象。

注入器负责持有一组绑定;当外界要求创建对象时,解析这些依赖并注入它们。

依赖就是将被用于注入的对象。

三者的关系图如下:

angular-di

(图片来源 —— Angular 权威指南)

接下来我们来看一下如何利用 Angular 重写上面的示例:

car.model.ts

export class Body {}

export class Doors {}

export class Engine {

  start() {

    console.log("?开动鸟~~~");

  }

}

export class Car {

  constructor(

    private engine: Engine,

    private doors: Doors,

    private body: Body

  ) {}

  run() {

    this.engine.start();

  }

}

app.component.ts

@Component({

  selector: "app-root",

  template: `

    <div>

     <h3>Angular DI</h3>

    </div>

  `

})

export class AppComponent {

  carInjector: Injector;

  constructor() {

    this.carInjector = Injector.create([

      { provide: Body, useClass: Body, deps: [] },

      { provide: Doors, useClass: Doors, deps: [] },

      { provide: Engine, useClass: Engine, deps: [] },

      { provide: Car, useClass: Car, deps: [Engine, Doors, Body] }

    ]);

    const newCar = this.createCar();

    newCar.run();

  }

  createCar(): Car {

    return this.carInjector.get(Car);

  }

}

Provider

Provider 的作用

在 Angular 中我们通过 Provider 来描述与 Token 相关联的依赖对象的创建方式。在 Angular 中依赖对象的创建方式分为以下四种:

useClass

useValue

useExisting

useFactory

Provider 的分类

在 Angular 中 Provider 主要分为:

ClassProvider

ValueProvider

ExistingProvider

FactoryProvider

Provider 的使用

ClassProvider

@Component({

  selector: 'drink-viewer',

  providers: [

    { provide: FoodService, useClass: FoodService }

  ],

})

ValueProvider

@NgModule({

  declarations: [

    AppComponent,

  ],

  providers: [

    { provide: 'api', useValue: '/api/pizzas' }

  ]

})

export class AppModule {}

ExistingProvider

@Component({

  selector: 'drink-viewer',

  providers: [

    FoodService,

    { provide: DrinkService, useExisting: FoodService }

  ]

})

FactoryProvider

export function SideFactory(http) {

  return new FoodService(http, '/api/sides');

}

@Component({

  selector: 'side-viewer',

  providers: [

    {

      provide: FoodService,

      useFactory: SideFactory,

      deps: [Http]

    }

  ],

})

在 ValueProvider 的示例中,我们使用字符串作为 token,在大多数情况下,是不会存在问题的。

{ provide: 'api', useValue: '/api/pizzas' }

但假设某一天我们引入了一个第三方库,该库内部也是使用 'api' 作为 token,这时候就会导致系统出现异常。

为了解决 token 冲突问题,Angular 引入了 InjectionToken 来避免出现 token 冲突。对于上面的示例,我们可以使用 InjectionToken 来创建一个唯一的 token:

export const API_TOKEN = new InjectionToken<string>('api');

使用的时候也很简单,只需要导入 API_TOKEN,然后更新 Provider 对象的 provide 属性,具体如下:


providers: [

  { provide: API_TOKEN, useValue: '/api/pizzas' }

]

最后我们来介绍一下 StaticProvider,Angular 为了提高应用的性能,引入了静态注入器和 StaticProvider。在引入 StaticProvider 之前,Angular 内部通过 Reflect API 自动解析依赖对象:

function _dependenciesFor(typeOrFunc: any): ReflectiveDependency[] {

  const params = reflector.parameters(typeOrFunc);

  //...

}

这个工作需要在运行时完成,而在 Angular 引入了静态注入器和 StaticProvider 之后,可以直接通过访问 Provider 对象的 provide 属性直接获取相应的依赖列表:

function computeDeps(provider: StaticProvider): DependencyRecord[] {

  let deps: DependencyRecord[] = EMPTY;

  const providerDeps: any[] =

      (provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;

}

这样在一定程度上,提高了应用程序的效率。最后感兴趣的同学,可以参考一下下图。

static-provider


明天计划的事情:(一定要写非常细致的内容) 
遇到的问题:(遇到什么困难,怎么解决的) 
收获:(通过今天的学习,学到了什么知识)


返回列表 返回列表
评论

    分享到