依赖注入(DI)和控制反转(IOC)基本是一个意思,由于提及来谁都离不开谁。 简单来讲,类A依赖类B,但A不控制B的建立和销毁,仅使用B,那么B的控制权则交给A以外处理,这叫控制反转(IOC)。 因为A依赖于B,所以在A中必然要使用B的instance,咱们能够经过A的构造函数将B的实例注入,好比:javascript
class B { }
class A {
constructor(b: B) {
console.log(b);
}
}
const b = new B();
// 将B的实例注入到a中
const a = new A(b);
复制代码
这个过程叫依赖注入(DI)。 那么什么是IOC Container(容器)? 在刚刚的例子中,将B的实例注入到A的构造函数中的这个过程是咱们手动操做的,比较麻烦,特别是当类的关系变多变复杂时,这种方式显得很难维护。 所以IOC容器就是为了解决这样的问题,IOC容器负责管理对象的生命周期、依赖关系等,实现对象的依赖查找以及依赖注入。 好比Java的Spring以及前端@Angular框架的依赖注入器(DI)就是属于IOC容器。前端
接下来我将经过代码的形式对比使用依赖注入相比非依赖注入的好处体如今哪。java
咱们先来看一段传统的实现代码(非DI) car.ts框架
// 引擎
export class Engine {
public cylinders = '引擎发动机1';
}
// 轮胎
export class Tires {
public make = '品牌';
}
export 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类没有经过第三方容器而是亲自建立了一个引擎(engine)和一些轮胎(tires),这样的代码耦合度比较高,这样会存在如下问题:函数
问题1:若是有一天对引擎进行升级,代码以下:性能
// 引擎
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}
复制代码
在建立引擎的时候须要传入一个参数,那么这时候就须要修改Car类里的new Engine(parameter),这样就致使Car类被破坏了,这里请思考一个问题:要怎么作才能使引擎升级的时候不须要修改Car类呢?(答案:DI)单元测试
问题2:若是想在Car上使用不一样品牌的轮胎,代码以下:测试
// 轮胎
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
//。。。。。。其余代码省略。。。。。。。
public tires: Tires;
constructor() {
this.tires = new Tires1();
}
}
复制代码
此时又得从新修改Car的代码,这里请思考一个问题:要怎么作才能使Car更换其余不一样品牌的轮胎的时候不须要修改Car类呢?(答案:DI)ui
问题3:如何实现数据共享,好比说车联网,创建了一个Service数据中心,不一样的Car经过Service实现数据通讯以及数据共享,若是是经过在Car里new Service的方式,是没法实现数据共享和通讯的,由于不一样Car里的Service不是同一个实例。this
这里请思考一个问题:如何实现不一样Car的数据通讯和共享呢?
问题4:测试比较难,根本没法测试。 在示例代码中,Car类依赖于Engine类和Tires类,而Engine和Tires又可能各自依赖于其余的类,而其余的类又可能有各自更多的依赖,在这样层层的依赖关系中,因为不能控制Car背后的隐藏依赖,要进行测试是比较难的,或者应该说,这样的代码是根本没法进行测试的。 好比说想同时测试不一样品牌的轮子的car的性能,由于car里头的new已经写死了,所以没法作到。 好比说想同时测试不一样参数的引擎的car的性能,由于car里头的new已经写死了,所以没法作到。 除非是每次只测试一种状况,下面拿测试不一样品牌的轮子来举例: 先测试品牌1的轮子: car.ts
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
public tires: Tires;
public description = 'No DI';
constructor() {
// new 一个品牌1的轮子
this.tires = new Tires1();
}
// Method using the engine and tires
drive() {
return `${this.description} car with ` + ` ${this.tires.make} tires.`;
}
}
复制代码
测试程序car.spec.ts
import { Car } from './car.ts';
describe('Car类单元测试', function () {
it('测试品牌1轮子的Car的驾驶性能', function () {
const car = new Car();
car.drive().should.equal('No DI car with 品牌1 tires.');
})
})
复制代码
以上代码对轮子品牌1进行测试,输出轮子品牌1的car的驾驶性能。 接着对轮子品牌2进行测试:修改Car类,将this.tires = new Tires1();修改成 this.tires = new Tires2();此时输出轮子品牌2的car的驾驶性能。
这样的测试效率是很低的,由于每次只能手动的测试一种状况,若是再加上引擎的测试,那多种混合状况就更多了,根本就不能作到自动测试,所谓的自动测试,是一次性将全部的状况都写到一个单元测试里,一次运行,全部状况都会被测试到,当测试经过了,那么就说明代码达到了预期。
针对以上问题,咱们来看看使用DI的好处。
接下来将演示使用DI来解决以上的4个问题。 先看使用DI实现的car.ts代码: car.ts
export class Engine {
public cylinders = '引擎发动机1';
}
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
public description = 'DI';
// 经过构造函数注入Engine和Tires
constructor(public engine: Engine, public tires: Tires) {}
// Method using the engine and tires
drive() {
return `${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
}
}
复制代码
在以上代码中,经过往构造函数中传入engine和tires来建立Car,Car类再也不亲自建立engine和tires,而是消费它们,此时最大的好处就是engine和tires与Car解除了强耦的关系。在new Car的时候,能够传入任何类型的Engine和Tires,即 let car = new Car(new Engine(),new Tires());
解决问题1:若是有一天对引擎进行升级,代码以下:
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}
复制代码
在建立引擎的时候须要传入一个参数,这时候不须要修改Car类,只须要修改主程序便可:
主程序代码:
main(){
const car = new Car(new Engine('引擎启动机2'), new Tires1());
car.drive();
}
复制代码
解决问题2:若是想在Car上使用不一样品牌的轮胎,代码以下:
export class Tires {
public make = '品牌';
}
export class Tire1 extends Tires {
public make = '品牌1';
}
export class Tire2 extends Tires {
public make = '品牌2';
}
export class Car {
//。。。。。。其余代码省略。。。。。。。
constructor(public engine: Engine, public tires: Tires) {}
}
复制代码
此时不须要修改Car类,只须要修改主程序便可: 主程序代码:
main(){
// 使用品牌2的轮胎
const car = new Car(new Engine('引擎启动机2'), new Tires2());
car.drive();
}
复制代码
解决问题3:如何实现数据共享,好比说车联网,创建一个Service数据中心(就像angular的Service层,能够给多个component共享),不一样的Car经过Service实现数据通讯以及数据共享。 代码以下: Service.ts
export class Service {
public data = '';
// 向Service存数据
setData(_data: string) {
this.data = _data;
}
// 从Service中取数据
getData() {
return this.data;
}
}
复制代码
car.ts
export class Car {
constructor(public service: Service) { }
// 向Service存数据
setDataToService(_data: string) {
this.service.setData(_data);
}
// 从Service中取数据
getDataFromService() {
return this.service.getData();
}
}
复制代码
此时主程序以下: 主程序代码:
main(){
// 建立一个共享服务中心Service
const shareService = new Service();
const car1 = new Car(shareService);
const car2 = new Car(shareService);
// car1向服务中心存数据
car1.setDataToService('this data is from car1.');
// car2从服务中心取数据
car2.getDataFromService();
}
复制代码
解决问题4:测试用例 在示例代码中,Car类依赖于Engine类和Tires类,而Engine和Tires又可能各自依赖于其余的类,而其余的类又可能有各自的依赖,在这样层层的依赖关系中,使用DI的代码测试是比较简单的。 测试程序以下: 测试程序 car.spec.ts
import { Car,Engine,Tires1, Tires2} from './car.ts';
// 测试程序入口
describe('Car类单元测试', function () {
const engine1 = new Engine('引擎发动机1');
const engine2 = new Engine('引擎发动机2');
const tires1 = new Tires1();
const tires2 = new Tires2();
it('测试引擎1 轮胎品牌1', function () {
const car = new Car(engine1, tires1);
car.drive().should.equal('DI car with 引擎发动机1 cylinders and 品牌1 tires.');
});
it('测试引擎1 轮胎品牌2', function () {
const car = new Car(engine1, tires2);
car.drive().should.equal('DI car with 引擎发动机1 cylinders and 品牌2 tires.');
});
it('测试引擎2 轮胎品牌1', function () {
const car = new Car(engine2, tires1);
car.drive().should.equal('DI car with 引擎发动机2 cylinders and 品牌1 tires.');
});
it('测试引擎2 轮胎品牌2', function () {
const car = new Car(engine2, tires2);
car.drive().should.equal('DI car with 引擎发动机2 cylinders and 品牌2 tires.');
});
})
复制代码
此时以为很棒有木有,自动测试的思想就是这样的,将全部的状况的代码都配置好,一次运行,全部的状况均可以测试到。
至此,若是看懂以上的话,DI的思想以及为何要用DI就应该能够理解了。