非常教程

AngularJS参考手册

依赖注入

依赖注入模式

依赖注入是一个很重要的设计模式。 它使用得非常广泛,以至于几乎每个人都把它简称为 DI

Angular 有自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。

本页会告诉你 DI 是什么,以及为什么它很有用。

当你学会了这种通用的模式之后,就可以转到 Angular 依赖注入 中去看看它在 Angular 应用中的工作原理了。

为什么需要依赖注入?

要理解为什么依赖注入这么重要,不妨先考虑不使用它的一个例子。想象下列代码:

src/app/car/car.ts (without DI)

content_copyexport class Car {   public engine: Engine;  public tires: Tires;  public description = 'No DI';   constructor() {    this.engine = new Engine();    this.tires = new Tires();  }   // Method using the engine and tires  drive() {    return `${this.description} car with ` +      `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;  }}

Car 类在自己的构造函数中创建了它所需的一切。 这样做有什么问题? 问题在于 Car 类是脆弱、不灵活以及难于测试的。

Car 类需要一个引擎 (engine) 和一些轮胎 (tire),它没有去请求现成的实例, 而是在构造函数中用具体的 EngineTires 类实例化出自己的副本。

如果 Engine 类升级了,它的构造函数要求传入一个参数,这该怎么办? 这个 Car 类就被破坏了,在把创建引擎的代码重写为 this.engine = new Engine(theNewParameter) 之前,它都是坏的。 当第一次写 Car 类时,你不关心 Engine 构造函数的参数,现在也不想关心。 但是,当 Engine 类的定义发生变化时,就不得不在乎了,Car 类也不得不跟着改变。 这就会让 Car 类过于脆弱。

如果想在 Car 上使用不同品牌的轮胎会怎样?太糟了。 你被锁定在 Tires类创建时使用的那个品牌上。这让 Car 类缺乏弹性。

现在,每辆车都有它自己的引擎。它不能和其它车辆共享引擎。 虽然这对于汽车来说还算可以理解,但是设想一下那些应该被共享的依赖,比如用来联系厂家服务中心的车载无线电。 这种车缺乏必要的弹性,无法共享当初给其它消费者创建的车载无线电。

当给 Car 类写测试的时候,你就会受制于它背后的那些依赖。 能在测试环境中成功创建新的 Engine 吗? Engine 自己又依赖什么?那些依赖本身又依赖什么? Engine 的新实例会发起到服务器的异步调用吗? 你当然不想在测试期间这么一层层追下去。

如果 Car 应该在轮胎气压低的时候闪动警示灯该怎么办? 如果没法在测试期间换上一个低气压的轮胎,那该如何确认它能正确的闪警示灯?

你没法控制这辆车背后隐藏的依赖。 当不能控制依赖时,类就会变得难以测试。

该如何让 Car 更强壮、有弹性以及可测试?

答案非常简单。把 Car 的构造函数改造成使用 DI 的版本:

src/app/car/car.ts (excerpt with DI)

src/app/car/car.ts (excerpt without DI)

content_copypublic description = 'DI';

constructor(public engine: Engine, public tires: Tires) { }

发生了什么?现在依赖的定义移到了构造函数中。 Car 类不再创建引擎 engine 或者轮胎 tires。 它仅仅“消费”它们。

这个例子又一次借助 TypeScript 的构造器语法来同时定义参数和属性。

现在,通过往构造函数中传入引擎和轮胎来创建一辆车。

content_copy// Simple car with 4 cylinders and Flintstone tires.
let car = new Car(new Engine(), new Tires());

酷!引擎和轮胎这两个依赖的定义与 Car 类本身解耦了。 只要喜欢,可以传入任何类型的引擎或轮胎,只要它们能满足引擎或轮胎的通用 API 需求。

这样一来,如果有人扩展了 Engine 类,那就不再是 Car 类的烦恼了。

Car消费者也有这个问题。消费者必须修改创建这辆车的代码,就像这样:

content_copyclass Engine2 {
  constructor(public cylinders: number) { }
}
// Super car with 12 cylinders and Flintstone tires.
let bigCylinders = 12;
let car = new Car(new Engine2(bigCylinders), new Tires());

这里的要点是:Car 本身不必变化。下面就来解决消费者的问题。

Car 类非常容易测试,因为现在你对它的依赖有了完全的控制权。 在每个测试期间,你可以往构造函数中传入 mock 对象,做想让它们做的事:

content_copyclass MockEngine extends Engine { cylinders = 8; }
class MockTires  extends Tires  { make = 'YokoGoodStone'; }

// Test car with 8 cylinders and YokoGoodStone tires.
let car = new Car(new MockEngine(), new MockTires());

刚刚学习了什么是依赖注入

它是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们。

酷!但是,可怜的消费者怎么办? 那些希望得到一个 Car 的人们现在必须创建所有这三部分了:CarEngineTiresCar 类把它的快乐建立在了消费者的痛苦之上。 需要某种机制为你把这三个部分装配好。

可以写一个巨型类来做这件事:

src/app/car/car-factory.ts

content_copyimport { Engine, Tires, Car } from './car'; // BAD pattern!export class CarFactory {  createCar() {    let car = new Car(this.createEngine(), this.createTires());    car.description = 'Factory';    return car;  }   createEngine() {    return new Engine();  }   createTires() {    return new Tires();  }}

现在只需要三个创建方法,这还不算太坏。 但是当应用规模变大之后,维护它将变得惊险重重。 这个工厂类将变成由相互依赖的工厂方法构成的巨型蜘蛛网。

如果能简单的列出想建造的东西,而不用定义该把哪些依赖注入到哪些对象中,那该多好!

到了依赖注入框架一展身手的时候了! 想象框架中有一个叫做注入器 (injector) 的东西。 用这个注入器注册一些类,它会弄明白如何创建它们。

当需要一个 Car 时,就简单的找注入器取车就可以了。

src/app/car/car-injector.ts

content_copylet car = injector.get(Car);

皆大欢喜。Car 不需要知道如何创建 EngineTires。 消费者不需要知道如何创建 Car。 开发人员不需要维护巨大的工厂类。 Car 和消费者只要简单地请求想要什么,注入器就会交付它们。

这就是“依赖注入框架”存在的原因。

现在,你知道什么是依赖注入以及它有什么优点了吧?那就请到 Angular 依赖注入 中去看看它在 Angular 中是如何实现的。

AngularJS

Angular 是一个开发平台。它能帮你更轻松的构建 Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。

AngularJS目录

1.快速上手 | quick start
2.语言服务
3.安全
4.环境准备与部署
5.Service Worker
6.保持最新
7.从 AngularJS 升级
8.服务端渲染
9.Visual Studio 2015 快速上手
10.风格指南
11.国际化
12.测试
13.路由与导航
14. 教程 | Tutorial
15.架构
16.组件与模板
17.表单
18.可观察对象与RxJS
19.引导启动
20.Angular 模块
21.依赖注入
22.HttpClient
23.词汇表
24.AngularJS 应用
25.AngularJS 模块
26.AngularJS 事件
27.AngularJS HTML DOM
28.AngularJS 过滤器
29.AngularJS 控制器
30.AngularJS 指令
31.AngularJS 表达式
32.AngularJS 简介
33.AngularJS 参考手册
34.AngularJS 实例
35.AngularJS 输入验证
36.AngularJS 表单
37.AngularJS SQL
38.AngularJS 表格
39.AngularJS Http
40.AngularJS 包含
41.AngularJS Bootstrap
42.AngularJS API
43.AngularJS ng-checked 指令
44.AngularJS ng-change 指令
45.AngularJS ng-blur 指令
46.AngularJS ng-bind-template 指令
47.AngularJS ng-bind-html 指令
48.AngularJS ng-bind 指令
49.AngularJS ng-app 指令
50.AngularJS Scope(作用域)
51.AngularJS ng-model 指令
52.AngularJS ng-dblclick 指令
53.AngularJS ng-cut 指令
54.AngularJS ng-csp 指令
55.AngularJS ng-copy 指令
56.AngularJS ng-controller 指令
57.AngularJS ng-cloak 指令
58.AngularJS ng-click 指令
59.AngularJS ng-class-odd 指令
60.AngularJS ng-class-even 指令
61.AngularJS ng-class 指令
62.AngularJS ng-keyup 指令
63.AngularJS ng-keypress 指令
64.AngularJS ng-keydown 指令
65.AngularJS ng-init 指令
66.AngularJS ng-include 指令
67.AngularJS ng-if 指令
68.AngularJS ng-href 指令
69.AngularJS ng-hide 指令
70.AngularJS ng-focus 指令
71.AngularJS ng-disabled 指令
72.AngularJS ng-non-bindable 指令
73.AngularJS ng-mouseup 指令
74.AngularJS ng-mouseover 指令
75.AngularJS ng-mousemove 指令
76.AngularJS ng-mouseleave 指令
77.AngularJS ng-mouseenter 指令
78.AngularJS ng-mousedown 指令
79.AngularJS ng-model-options 指令
80.AngularJS ng-model 指令
81.AngularJS ng-list 指令
82.AngularJS ng-style 指令
83.AngularJS ng-srcset 指令
84.AngularJS ng-src 指令
85.AngularJS ng-show 指令
86.AngularJS ng-selected 指令
87.AngularJS ng-repeat 指令
88.AngularJS ng-readonly 指令
89.AngularJS ng-paste 指令
90.AngularJS ng-options 指令
91.AngularJS ng-open 指令
92.AngularJS ng-value 指令
93.AngularJS ng-switch 指令
94.AngularJS ng-submit 指令
95.AngularJS 服务(Service)
96.AngularJS Select(选择框)
97.AngularJS 动画
98.AngularJS 依赖注入