依赖注入(1):基础
为什么需要依赖注入:生产者的苦恼
一个car对象依赖于engine对象和tire对象,也就是说car对象内部存在对engine的“调用”。
那么就存在多种方法去引用engine:在内部创建,构造器注入,属性注入,方法注入等。
原始的办法:在类的构造函数中,直接实例化engine和tire。
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
}
问题在于:如果engine和tire的实例化方法发生变化(例如,需要传进参数),那么不得不去修改car的构造函数。
我们将这个问题称为:car类过于脆弱,缺乏弹性,难以测试。
如何实现依赖注入:生产者的转变
改进方向:让car更强壮,有弹性,可测试。
改进方式:把car的构造函数改造成使用DI的版本。
constructor(public engine: Engine, public tires: Tires) { }
嗯?这是啥?
我们把依赖的定义移到了构造函数中。 “Car类不再创建引擎或者轮胎。 它仅仅‘消费’它们。”
嗯?什么意思?
我们看一下改进之后,我们如何创建一辆车。
let car = new Car(new Engine(), new Tires());
我们可以看到,只要满足API需求的engine和tire都可以传进来。
如果我们有一个新的engine,叫engine2。
class Engine2 {
constructor(public cylinders: number) { }
}
此时我们创建带有engine2的车。
let car = new Car(new Engine2(12), new Tires());
通过依赖注入,我们可以很容易地在外部模拟出各类的engine和tire。
从而在不改变car的情况下,注入car,对car进行测试。
let car = new Car(new MockEngine(), new MockTires());
至此,我们大概了解了什么是依赖注入。
依赖注入是一种编程模式,它可以让类从外部获得他所依赖的东西,而不需要亲自创建他们。
巨型工厂:消费者的苦恼
在引入依赖注入以后,我们想创建一辆车,我们需要自己创建engine和tire。
我们自然而然地可以建造一个巨型工厂来帮助我们实现各种依赖实例的创建。
import { Engine, Tires, Car } from './car';
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时,就简单的找注入器取车就可以了。
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]);
let car = injector.get(Car);