前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Angular 依赖注入简介

Angular 依赖注入简介

作者头像
阿宝哥
发布2019-11-05 15:47:54
7000
发布2019-11-05 15:47:54
举报
文章被收录于专栏:全栈修仙之路

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

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

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

  1. 定义车身类
代码语言:javascript
复制
export default class Body { }
  1. 定义车门类
代码语言:javascript
复制
export default class Doors { }
  1. 定义车引擎类
代码语言:javascript
复制
export default class Engine {
  start() {
    console.log('开动鸟~~~');
  }
}
  1. 定义汽车类
代码语言:javascript
复制
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();
    }
}

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

代码语言:javascript
复制
let car = new Car(); // 造辆新车
car.run(); // 开车上路咯

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

  • 问题一:在创建新车的时候,你没有选择,假设你想更换汽车引擎的话,按照目前的方案,是实现不了的。
  • 问题二:在汽车类内部,你需要在构造函数中手动去创建各个部件。

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

代码语言:javascript
复制
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 类,我们来重新造辆新车:

代码语言:javascript
复制
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
no-di

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

di
di

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

  • 提供者负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表。它告诉 Angular 该如何根据指定的令牌创建对象。
  • 注入器负责持有一组绑定;当外界要求创建对象时,解析这些依赖并注入它们。
  • 依赖就是将被用于注入的对象。

三者的关系图如下:

angular-di
angular-di

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

car.model.ts

代码语言:javascript
复制
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

代码语言:javascript
复制
@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 的使用
  1. ClassProvider
代码语言:javascript
复制
@Component({
  selector: 'drink-viewer',
  providers: [
    { provide: FoodService, useClass: FoodService }
  ],
})
  1. ValueProvider
代码语言:javascript
复制
@NgModule({
  declarations: [
    AppComponent,
  ],
  providers: [
    { provide: 'api', useValue: '/api/pizzas' }
  ]
})
export class AppModule {}
  1. ExistingProvider
代码语言:javascript
复制
@Component({
  selector: 'drink-viewer',
  providers: [
    FoodService,
    { provide: DrinkService, useExisting: FoodService }
  ]
})
  1. FactoryProvider
代码语言:javascript
复制
export function SideFactory(http) {
  return new FoodService(http, '/api/sides');
}

@Component({
  selector: 'side-viewer',
  providers: [
    {
      provide: FoodService,
      useFactory: SideFactory,
      deps: [Http]
    }
  ],
})

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

代码语言:javascript
复制
{ provide: 'api', useValue: '/api/pizzas' }

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

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

代码语言:javascript
复制
export const API_TOKEN = new InjectionToken<string>('api');

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

代码语言:javascript
复制
providers: [
  { provide: API_TOKEN, useValue: '/api/pizzas' }
]

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

代码语言:javascript
复制
function _dependenciesFor(typeOrFunc: any): ReflectiveDependency[] {
  const params = reflector.parameters(typeOrFunc);
  //...
}

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

代码语言:javascript
复制
function computeDeps(provider: StaticProvider): DependencyRecord[] {
  let deps: DependencyRecord[] = EMPTY;
  const providerDeps: any[] =
      (provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;
}

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

static-provider
static-provider
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/08/11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 依赖注入的概念
  • Provider
    • Provider 的作用
      • Provider 的分类
        • Provider 的使用
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档