Angular 2(四): 依赖注入和控制反转

基础概念

依赖注入和控制反转

  • A依赖于B,A不需要在代码内部直接创建B的实例对象,而是由外部提供(B的实例对象);
  • 将依赖的控制权从代码的内部转移到代码的外部;

依赖注入和控制反转是一体两面,依赖注入是目的,控制反转是手段,终究实现代码的松耦合。

token

  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    @NgModule({
    ①providers: [ ProdService ]
    ②providers: [{
    provide: ProdService,
    useClass: ProdService,
    }]
    })
  • token在Angular中代表一个被注入的对象的类型,由provide决定;
  • 如:示例代码中,注册的Token的类型为ProdService;
  • 当有组件或者指令声明(在其构造函数中)需要一个类型为ProdService的token时,注入器就会实例化一个ProdService对象(由useClass决定实例化哪个),并将其注入到组件中;
  • 在组件中,通过构造函数获得该对象
    1
    2
    3
    constructor(private prodService:ProdService){
    //prodService
    }

服务的三种使用形式:useClass、useFactory、useValue

@Injectable(),表示可以在该类中注入其他服务,建议所有的服务都加上该声明;

useClass

  • 服务建议放在模块(即:app.module.ts的providers中)中,使之成为全局共享;
  • 如果将服务放在组件中,那么它只能在局部使用(组件及其子组件)。对于同名的token请求,组件中的服务类将覆盖模块中的服务类;

1.productService.ts

1
2
3
4
5
6
7
@Injectable()
export class ProductService{
//do something
getProduct():Product{
return new Product(...)
}
}

2.anthorProductService.ts

1
2
3
4
5
6
7
@Injectable()
export class AnthorProductService implements ProductService{
//do something
getProduct():Product{
return new Product(...);
}
}

3.app.module.ts

1
2
3
@NgModule({
providers:[ ProductService ]
})

4.product.component.html

使用在模块中声明的服务

1
2
3
4
5
6
@Component({
...,
})
export class ProductComponent implements OnInit{
constructor(private ps:ProductService){}
}

使用在组件中的服务

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component({
...,
providers:[
{
provide:ProductService,
useClass:AnthorProductService
}
]
})
export class ProductComponent implements OnInit{
//声明使用ProductService类型的token,会使用AnthorProductService类创建实例
constructor(private ps:ProductService){}
}


提供器之 useFactory

在提供服务时,可能要根据不同的状态去初始化不同的服务实例,这时需要使用useFactory提供器来完成。

通过随机数模拟开发/生产状态来实例化不同的服务类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
providers:[
{
provide: ProductService,
useFactory:()=>{
① let log = new LogService();
let dev = Math.random() > 0.5;
if(dev){
return new ProductService(log);
}
else {
return new AnthorProductService(log);
}
}
},
LogService
],

  • 以上,我们就完成了模拟开发/生产环境来提供不同的服务实例,而在组件内部使用的时候根本不知道我们提供的是什么。这也就实现了服务和组件的初步解耦。
  • 同时,如果你细心一点,在使用的过程中会发现整个应用内部ProductService类型的token对应的服务实例是同一个。由此可以说:工厂方法创建的对象是单例对象,在该对象被初次创建之后,整个应用内部都是使用同一个。
  • 但是,轻而易举地发现在useFactory的方法内部出现了①这样的写法,LogService和ProductService强耦合。我们需要解决这个问题,使用提供器的第三个参数配置deps(数组):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
provide: ProductService,
useFactory:(log:LogService)=>{
let dev = Math.random() > 0.5;
if(dev){
return new ProductService(log);
}
else {
return new AnthorProductService(log);
}
},
deps:[ LogService ]
},
LogService
  • 上面代码实现了服务(ProductService)和服务(LogService)之间的解耦。ProductService将使用外部的LogService来注入工厂方法内部,从而创建实例。

提供器之 useValue

该提供器方法常常结合useFactory使用

1
2
3
4
5
6
7
8
9
10
11
12
13
providers:[
{
provide...,
useFactory:(log:LogService,isDev)=>{
//do something
},
deps:[ LogService,"IS_DEV" ]
},
{
provide:"IS_DEV",
useValue:false
}
]

  • useValue的值也可以使用对象:
    1
    2
    3
    4
    {
    provide:"APP_CONFIG",
    useValue:{ isDev:false }
    }

Angular在启动的时候,在创建服务的过程中会对每一个服务的依赖进行实例化并注入到当前服务中,如此一层一层递进直至结束。于是

  • 每一个服务都是独立的,服务与服务之间不存在耦合。
  • 每一个组件在申请使用服务的时候,并不知道服务是如何构建的,组件与服务之间不存在耦合。

以上两点就是Angular实现“依赖注入”和“控制反转”的核心。