12 KiB
The Dependency Injection pattern
依赖注入(Dependency injection)模式
Dependency injection is an important application design pattern. It's used so widely that almost everyone just calls it DI.
依赖注入是一个很重要的设计模式。 它使用得非常广泛,以至于几乎每个人都把它简称为 DI 。
Angular has its own dependency injection framework, and you really can't build an Angular application without it.
Angular 有自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。
This page covers what DI is and why it's useful.
本页会告诉你 DI 是什么,以及为什么它很有用。
When you've learned the general pattern, you're ready to turn to the Angular Dependency Injection guide to see how it works in an Angular app.
当你学会了这种通用的模式之后,就可以转到 Angular 依赖注入 中去看看它在 Angular 应用中的工作原理了。
{@a why-di }
Why dependency injection?
为什么需要依赖注入?
To understand why dependency injection is so important, consider an example without it. Imagine writing the following code:
要理解为什么依赖注入这么重要,不妨先考虑不使用它的一个例子。想象下列代码:
The Car
class creates everything it needs inside its constructor.
What's the problem?
The problem is that the Car
class is brittle, inflexible, and hard to test.
Car
类在自己的构造函数中创建了它所需的一切。
这样做有什么问题?
问题在于 Car
类是脆弱、不灵活以及难于测试的。
This Car
needs an engine and tires. Instead of asking for them,
the Car
constructor instantiates its own copies from
the very specific classes Engine
and Tires
.
Car
类需要一个引擎 (engine) 和一些轮胎 (tire),它没有去请求现成的实例,
而是在构造函数中用具体的 Engine
和 Tires
类实例化出自己的副本。
What if the Engine
class evolves and its constructor requires a parameter?
That would break the Car
class and it would stay broken until you rewrote it along the lines of
this.engine = new Engine(theNewParameter)
.
The Engine
constructor parameters weren't even a consideration when you first wrote Car
.
You may not anticipate them even now.
But you'll have to start caring because
when the definition of Engine
changes, the Car
class must change.
That makes Car
brittle.
如果 Engine
类升级了,它的构造函数要求传入一个参数,这该怎么办?
这个 Car
类就被破坏了,在把创建引擎的代码重写为 this.engine = new Engine(theNewParameter)
之前,它都是坏的。
当第一次写 Car
类时,我们不关心 Engine
构造函数的参数,现在也不想关心。
但是,当 Engine
类的定义发生变化时,就不得不在乎了,Car
类也不得不跟着改变。
这就会让 Car
类过于脆弱。
What if you want to put a different brand of tires on your Car
? Too bad.
You're locked into whatever brand the Tires
class creates. That makes the
Car
class inflexible.
如果想在 Car
上使用不同品牌的轮胎会怎样?太糟了。
我们被锁定在 Tires
类创建时使用的那个品牌上。这让 Car
类缺乏弹性。
Right now each new car gets its own engine
. It can't share an engine
with other cars.
While that makes sense for an automobile engine,
surely you can think of other dependencies that should be shared, such as the onboard
wireless connection to the manufacturer's service center. This Car
lacks the flexibility
to share services that have been created previously for other consumers.
现在,每辆车都有它自己的引擎。它不能和其它车辆共享引擎。 虽然这对于汽车来说还算可以理解,但是设想一下那些应该被共享的依赖,比如用来联系厂家服务中心的车载无线电。 我们的车缺乏必要的弹性,无法共享当初给其它消费者创建的车载无线电。
When you write tests for Car
you're at the mercy of its hidden dependencies.
Is it even possible to create a new Engine
in a test environment?
What does Engine
depend upon? What does that dependency depend on?
Will a new instance of Engine
make an asynchronous call to the server?
You certainly don't want that going on during tests.
当给 Car
类写测试的时候,我们就会受制于它背后的那些依赖。
能在测试环境中成功创建新的 Engine
吗?
Engine
自己又依赖什么?那些依赖本身又依赖什么?
Engine
的新实例会发起到服务器的异步调用吗?
我们当然不想在测试期间这么一层层追下去。
What if the Car
should flash a warning signal when tire pressure is low?
How do you confirm that it actually does flash a warning
if you can't swap in low-pressure tires during the test?
如果 Car
应该在轮胎气压低的时候闪动警示灯该怎么办?
如果没法在测试期间换上一个低气压的轮胎,那该如何确认它能正确的闪警示灯?
You have no control over the car's hidden dependencies. When you can't control the dependencies, a class becomes difficult to test.
我们没法控制这辆车背后隐藏的依赖。 当不能控制依赖时,类就会变得难以测试。
How can you make Car
more robust, flexible, and testable?
该如何让 Car
更强壮、有弹性以及可测试?
{@a ctor-injection}
That's super easy. Change the Car
constructor to a version with DI:
答案非常简单。把 Car
的构造函数改造成使用 DI 的版本:
See what happened? The definition of the dependencies are
now in the constructor.
The Car
class no longer creates an engine
or tires
.
It just consumes them.
发生了什么?我们把依赖的定义移到了构造函数中。
Car
类不再创建引擎 engine
或者轮胎 tires
。
它仅仅“消费”它们。
This example leverages TypeScript's constructor syntax for declaring parameters and properties simultaneously.
这个例子又一次借助 TypeScript 的构造器语法来同时定义参数和属性。
Now you can create a car by passing the engine and tires to the constructor.
现在,通过往构造函数中传入引擎和轮胎来创建一辆车。
How cool is that?
The definition of the engine
and tire
dependencies are
decoupled from the Car
class.
You can pass in any kind of engine
or tires
you like, as long as they
conform to the general API requirements of an engine
or tires
.
酷!引擎和轮胎这两个依赖的定义与 Car
类本身解耦了。
只要喜欢,可以传入任何类型的引擎或轮胎,只要它们能满足引擎或轮胎的通用 API 需求。
Now, if someone extends the Engine
class, that is not Car
's problem.
这样一来,如果有人扩展了 Engine
类,那就不再是 Car
类的烦恼了。
The consumer of Car
has the problem. The consumer must update the car creation code to
something like this:
Car
的_消费者_也有这个问题。消费者必须更新创建这辆车的代码,就像这样:
The critical point is this: the Car
class did not have to change.
You'll take care of the consumer's problem shortly.
这里的要点是:Car
本身不必变化。下面就来解决消费者的问题。
The Car
class is much easier to test now because you are in complete control
of its dependencies.
You can pass mocks to the constructor that do exactly what you want them to do
during each test:
Car
类非常容易测试,因为现在我们对它的依赖有了完全的控制权。
在每个测试期间,我们可以往构造函数中传入 mock 对象,做想让它们做的事:
You just learned what dependency injection is.
刚刚学习了什么是依赖注入
It's a coding pattern in which a class receives its dependencies from external sources rather than creating them itself.
它是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们。
Cool! But what about that poor consumer?
Anyone who wants a Car
must now
create all three parts: the Car
, Engine
, and Tires
.
The Car
class shed its problems at the consumer's expense.
You need something that takes care of assembling these parts.
酷!但是,可怜的消费者怎么办?
那些希望得到一个 Car
的人们现在必须创建所有这三部分了:Car
、Engine
和 Tires
。
Car
类把它的快乐建立在了消费者的痛苦之上。
需要某种机制为我们把这三个部分装配好。
You could write a giant class to do that:
可以写一个巨型类来做这件事:
It's not so bad now with only three creation methods. But maintaining it will be hairy as the application grows. This factory is going to become a huge spiderweb of interdependent factory methods!
现在只需要三个创建方法,这还不算太坏。 但是当应用规模变大之后,维护它将变得惊险重重。 这个工厂类将变成由相互依赖的工厂方法构成的巨型蜘蛛网。
Wouldn't it be nice if you could simply list the things you want to build without having to define which dependency gets injected into what?
如果能简单的列出想建造的东西,而不用定义该把哪些依赖注入到哪些对象中,那该多好!
This is where the dependency injection framework comes into play. Imagine the framework had something called an injector. You register some classes with this injector, and it figures out how to create them.
到了依赖注入框架一展身手的时候了! 想象框架中有一个叫做_注入器 (injector)_ 的东西。 用这个注入器注册一些类,它会弄明白如何创建它们。
When you need a Car
, you simply ask the injector to get it for you and you're good to go.
当需要一个 Car
时,就简单的找注入器取车就可以了。
Everyone wins. The Car
knows nothing about creating an Engine
or Tires
.
The consumer knows nothing about creating a Car
.
You don't have a gigantic factory class to maintain.
Both Car
and consumer simply ask for what they need and the injector delivers.
皆大欢喜。Car
不需要知道如何创建 Engine
和 Tires
。
消费者不需要知道如何创建 Car
。
开发人员不需要维护巨大的工厂类。
Car
和消费者只要简单地请求想要什么,注入器就会交付它们。
This is what a dependency injection framework is all about.
这就是“依赖注入框架”存在的原因。
Now that you know what dependency injection is and appreciate its benefits, turn to the Angular Dependency Injection guide to see how it is implemented in Angular.
现在,你知道什么是依赖注入以及它有什么优点了吧?那就请到 Angular 依赖注入 中去看看它在 Angular 中是如何实现的。