在介绍依赖注入的概念和作用前,我们先来看个例子。各位同学请睁大眼睛,我要开始 “闭门造车” 了。
一辆车内部构造很复杂,出于简单考虑,我们就只考虑三个部分:车身、车门和引擎。
现在我们来分别定义各个部分:
- 定义车身类
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();
}
}
一切已准备就绪,我们马上来造一辆车:
代码语言: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:
Angular 利用依赖注入机制改变了这一点,在该机制下,如果服务 A 中需要服务 B,即服务 A 依赖于服务 B,那么我们期望服务 B 能被自动注入到服务 A 中,如下图所示:
在 Angular 中,依赖注入包括以下三个部分:
- 提供者负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表。它告诉 Angular 该如何根据指定的令牌创建对象。
- 注入器负责持有一组绑定;当外界要求创建对象时,解析这些依赖并注入它们。
- 依赖就是将被用于注入的对象。
三者的关系图如下:
接下来我们来看一下如何利用 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 的使用
- 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,在大多数情况下,是不会存在问题的。
代码语言: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 属性,具体如下:
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;
}
这样在一定程度上,提高了应用程序的效率。最后感兴趣的同学,可以参考一下下图。