0%

对 DI(依赖注入) 和 IOC(控制反转) 的理解

对 DI(依赖注入) 和 IOC(控制反转) 的理解

简单来说,类A依赖类B,但A不控制B的创建和销毁,仅使用B,那么B的控制权则交给A之外处理,这叫控制反转(IOC)

由于A依赖于B,因此在A中必然要使用B的实例,我们可以通过A的构造函数将B的实例注入。

1
2
3
4
5
6
7
8
9
class B { }
class A {
constructor(b: B) {
console.log(b);
}
}
const b = new B();
// 将B的实例注入到a中
const a = new A(b);

和原来的主要区别在于: 之前是在 class A 之中实例化 B,现在是直接把实例作为参数传入。

接下来我将通过代码的形式对比使用依赖注入相比非依赖注入的好处体现在哪。

非依赖注入代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引擎 
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:如果有一天对引擎进行升级,代码如下:

1
2
3
4
5
6
7
// 引擎  
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}

在创建引擎的时候需要传入一个参数**,那么这时候就需要修改Car类里的new Engine(parameter)**,这样就导致修改Engine 类时同时需要去维护所有使用他的其他类,并且实际开发过程中,开发人员很可能不是同一个人,沟通和交流又需要成本。这就是耦合。

这里请思考一个问题:要怎么做才能使引擎升级的时候不需要修改Car类呢?(答案:DI)

问题2:如果想在Car上使用不同品牌的轮胎,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 轮胎
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的代码

问题3:如何实现数据共享,比如说车联网,建立了一个Service数据中心,不同的Car通过Service实现数据通信以及数据共享,如果是通过在Car里new Service的方式,是无法实现数据共享和通信的,因为不同Car里的Service不是同一个实例。

问题4:测试比较难,根本无法测试。 在示例代码中,Car类依赖于Engine类和Tires类,而Engine和Tires又可能各自依赖于其他的类,而其他的类又可能有各自更多的依赖,在这样层层的依赖关系中,由于不能控制Car背后的隐藏依赖,要进行测试是比较难的,或者应该说,这样的代码是根本无法进行测试的。 比如说想同时测试不同品牌的轮子的car的性能,因为car里头的new已经写死了,因此无法做到。 比如说想同时测试不同参数的引擎的car的性能,因为car里头的new已经写死了,因此无法做到。

使用依赖注入(DI)

接下来将演示使用DI来解决以上的4个问题。 先看使用DI实现的car.ts代码: car.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:如果有一天对引擎进行升级,代码如下:

1
2
3
4
5
6
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}

在创建引擎的时候需要传入一个参数,这时候不需要修改Car类,只需要修改主程序即可。(原本我们需要先修改 Car 类,再修改主程序,现在只需要修改主程序)

主程序代码:

1
2
3
4
main(){
const car = new Car(new Engine('引擎启动机2'), new Tires1());
car.drive();
}

解决问题2:如果想在Car上使用不同品牌的轮胎,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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类,只需要修改主程序即可: 主程序代码:

1
2
3
4
5
6
7
8
main(){
// 使用品牌1的轮胎
const car1 = new Car(new Engine('引擎启动机1'), new Tires1());
car.drive();
// 使用品牌2的轮胎
const car2 = new Car(new Engine('引擎启动机2'), new Tires2());
car.drive();
}

解决问题3:如何实现数据共享,比如说车联网,建立一个Service数据中心(就像angular的Service层,可以给多个component共享),不同的Car通过Service实现数据通信以及数据共享。 代码如下: Service.ts

1
2
3
4
5
6
7
8
9
10
11
export class Service {
public data = '';
// 向Service存数据
setData(_data: string) {
this.data = _data;
}
// 从Service中取数据
getData() {
return this.data;
}
}

cart.ts

1
2
3
4
5
6
7
8
9
10
11
export class Car {
constructor(public service: Service) { }
// 向Service存数据
setDataToService(_data: string) {
this.service.setData(_data);
}
// 从Service中取数据
getDataFromService() {
return this.service.getData();
}
}

此时主程序如下: 主程序代码:

1
2
3
4
5
6
7
8
9
10
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();
}

参考文章:

作者:SimpleXD, 链接:https://juejin.cn/post/6844903740953067534