美文网首页
浅谈angular2中的依赖注入

浅谈angular2中的依赖注入

作者: OnePiece索隆 | 来源:发表于2017-07-24 22:19 被阅读0次

    引言

    依赖注入,有后端背景的童鞋(尤其是熟悉java spring框架的)应该不会陌生,提到依赖注入,就不得不说一下控制反转,因为依赖注入实际上是控制反转概念的一种实现模式。

    控制反转(inversion of control,IoC)原则的非正式称谓是“好莱坞法则”,它来自好莱坞的一句常用语“别打给我们,我们会打给你(don't call us, we'll call you)”,从控制权的角度来看,控制权从我被反转到了别人,我从打电话这个行为的发出者,变成了打电话这个行为的接受者。

    控制“正”转可以理解为面向过程编程,定义一个main函数,在main函数里面定义所有的参与者与他们之间的交互过程,一点不能偷懒。需要用到某个对象某个类,自己去new,需要某个库函数,自己去主动调用。

    控制“反”转可以理解为面向对象编程,我们有了各种适用于不同场景下的框架和设计模式,这些事物帮我们搭建好了一个整体架构,这个架构用于准备一些基本而普遍需要的基础物资。

    控制反转的设计目标

    • 将执行任务这个动作和任务(具体实现)解耦
    • 让模块专注于它自己的设计目标。不需要考虑别的模块实现了什么,是如何实现的,模块之间依赖于接口
    • 让符合相同契约的模块能够互相替代

    遵循控制反转的几个事物

    • 框架:有些太繁琐太基础的事情我已经帮你做好了,我做不了的那部分以坑的形式留给你了,填完坑你的工作也就完成了,后边的事情我来处理
    • 回调、调度器、事件循环:把坑填好,等到时机成熟我就来和你相会
      策略模式:坑的游戏规则我已经定义好(输入输出),你只需要按规则填坑就好
    • 模板方式模式:坑的种类和踩坑的顺序我已定义好,你只需要负责填坑
    • 工厂模式和依赖注入类似,下面详细分析

    依赖注入

    在任何一个有请求作用的系统当中,至少需要有两个类互相配合工作,在一个入口类下使用new关键字创建另一个类的对象实例,“我”充当一个入口类,在这个入口类中,我每次吃饭的时候都要买一双一次性筷子(每一次使用都要new一次),在这样的关系下,是“我”(即调用者)每次都要“主动”去买一次性筷子(另一个类),是我控制了筷子,在这种控制正转的关系下,放在现实生活当中,肯定是不现实的,而且人是懒惰的,他总会去创造出更加方便自己生活的想法,更确切的做法是,买一双普通的筷子(非一次性),把他放在一个“框架”当中,你需要使用的时候就对“框架”说:我想要用筷子(向框架发出请求),接着筷子就会"注入"到你的手上,而在这个过程当中,你不再是控制方,反而演变成一名请求者(虽然本身还是调用者),依赖于框架给予你资源,控制权落到了框架身上,这就是依赖注入的设计原理。

    要理解依赖注入,就必须搞清楚几个问题:
    参与者:都有谁
    依赖:谁依赖于谁,为什么需要依赖
    注入:谁注入谁,到底注入什么
    控制:谁控制谁,控制什么

    参与者:
    一般有三方参与者,参与者1:某个对象,参与者2:DI框架,参与者3:对象所需的外部资源(类,文件等等)
    依赖:
    对象依赖于DI框架
    为什么需要依赖:
    “对象(参与者1)”需要“DI框架(参与者2)”提供所需的“外部资源(参与者3)”
    谁注入谁:
    DI框架注入“对象(参与者1)”
    注入什么:
    注入“对象(参与者1)”所需的“外部资源(参与者3)”
    谁控制谁:
    “DI框架(参与者2)”控制对象(参与者1)”
    控制什么:
    主要是控制对象所需外部资源实例的创建,管理所需重要对象的生命周期
    反转:
    在传统方式下,也就是正向,如果部件A需要依赖部件B,那就意味着A在内部要创建一个B的实例,也就是A依赖于B。在依赖注入机制也就是反向,如果在部件A中用到部件B,我们就应该期待B被传给A

    正向
    反向

    依赖注入:它的作用是让框架帮你处理重要对象的生命周期的管理,不需要你显式地进行管理(对象构造和销毁)
    优势:架构松耦合,测试更简单

    Angular 依赖注入

    在angular中,依赖注入包括三部分

    • 提供商:负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表,它告诉angular该如何根据指定的令牌创建对象
    • 注入器:负责持有一组绑定,当外界要求创建对象时,解析这些依赖并注入它们。我们不需要创建angular注入器,angular在启动过程中会自动为我们创建一个应用级注入器(platformBrowserDynamic().bootstrapModule(AppModule)
    • 依赖:被用于注入的对象
    注入器的提供商
    1. 类提供商 useClass
      providers: [Logger],这其实是注册提供商的简写表达式,完整的应该是providers: [{provide: Logger,useClass: Logger}]
      provide是令牌,用于定位依赖值和注册提供商
      useClass是提供商,用于定义对象,它用来指出注入什么以及如何注入
      某些时候,我们会请求一个不同的类来提供服务:
      providers: [{provide: Logger,useClass: BetterLogger}]
    2. 别名提供商 useExisting
      制造一个别名来引用以前注册过的令牌,比如:
      某个旧组件依赖一个OldLogger类,OldLogger和NewLogger具有相同的接口,但是由于某些原因, 我们不能升级这个旧组件并使用它。当旧组件想使用OldLogger记录消息时,我们希望改用NewLogger的单例对象来记录,不管组件请求的是新的还是旧的日志服务,依赖注入器注入的都应该是同一个单例对象。 也就是说,OldLogger应该是NewLogger的别名。如果使用useClass应用中会有两个不同的NewLogger实例,这显然不是我们想看到的,这时候就可以使用useExisting
    providers: [
          NewLogger,
          {provide: OldLogger,useClass: NewLogger} //创建NewLogger的两个实例
    ]
    
          NewLogger,
          {provide: OldLogger,useExisting: NewLogger} //只创建NewLogger的一个实例
    ]
    
    1. 值提供商 useValue
      当我们需要一个常量,而它可能会根据应用的其他部分甚至环境进行重定义时,这种方式非常重要。
      官方推荐使用InjectionToken作为令牌
    import { InjectionToken } from '@angular/core';
    export const TITLE = new InjectionToken <string>('title');
    providers:[
           {provide:TITLE, useValue: 'Hero of the Month'}
    ]
    
    1. 工厂提供商 useFactory
      使用工厂提供商进行注入,需要写一个返回任意对象的函数,工厂是创建可诸如对象的最强方式,因为我们可以在工厂函数中“为所欲为”。
    providers:[{
            provide: MyComponent,
            useFactory: ()=> {
                if(loggedIn) {
                   return new MyloggedComponent();
                }
                return new MyComponent();
            }
    }]
    
    依赖注入令牌

    1. 我们最常用的
      providers: [{provide: Logger,useClass: BetterLogger}]
    2. 类-接口
      使用没有被继承的抽象类作为依赖注入令牌,这种用法的类叫做:类-接口,它的好处是:提供了接口的强类型,能像正常类一样把它当做提供商令牌使用。类-接口应该只定义允许它消费者调用的成员,窄的接口有助于解耦该类的具体实现和它的消费者。
      不能使用接口作为依赖注入令牌,因为在JavaScript中并没有接口的概念,编译后angular无法识别
      更详细内容参考angular官方文档
    3. InjectionToken
      有时候依赖对象并不是一个类,它可能是一个简单的值,比如日期,数字和字符串,或者一个无形的对象,比如数组和函数。
      这样的对象没有应用程序接口,所以不能用一个类来表示,更适合表示它们的是:唯一的和符号性的令牌,一个JavaScript对象,拥有一个友好的名字,但不会与其它的同名令牌发生冲突。
      官方推荐使用InjectionToken实现(其实直接使用字符串也可以,最好还是按照官方推荐方式O(∩_∩)O)
    import { InjectionToken } from '@angular/core';
    export const TITLE = new InjectionToken <string>('title');
    export const RUNNERS_UP= new InjectionToken <string>('RunnersUP');
    providers:[
           {provide:TITLE, useValue: 'Hero of the Month'},
           {RUNNERS_UP, useFactory: runnersUpFactory(2),dep[Hero, HeroService]}
    ]
    
    注册提供商

    可以在NgModule或者组件中注册提供商
    通常推荐在对应的模块(NgModule)中注册提供商,除非你必须把服务实例的范围限制到某个组件及其子组件树

    1. 在NgModule中注册提供商
    @NgModule({
        imports:[],
        declarations:[],
        providers:[ HeroService ]
    })
    

    2.在组件中注册提供商

    @Component({
        selector:'my-heros',
        template:`<h2></h2>`
        providers:[ HeroService ]
    })
    
    服务

    在写一个服务时必须注意@Injectable() 是必不可少的,除非你不打算注入这个服务,因为@Injectable()相当于C++中的new()

    import { Injectable } from '@angular/core';
    @Injectable()
    export class HeroService {
    }
    

    如何使用已注册的服务?在组件或者服务的构造函数中注入即可

    constructor(
        public heroService: HeroService 
      ) {}
    

    所有被注入的服务在angular中总是单例的,肯定有童鞋要问了,那我需要多实例的怎么办?
    angular应用程序有多个依赖注入器,组成了一个与组件树平行的树状结构,所以可以在任何组件级别提供和建立服务,当组件申请一个依赖时,angular会从该组件本身的注入器开始,沿着依赖注入器的树向上查找,所以要实现多实例,在组件内注入服务即可

    @host @Optional
    有时候我们想限定依赖查找方式,比如只想让组件向上搜索到宿主组件,可以使用@host,当组件一层层向上查找没有找到注册的服务时,就会抛出错误,如果不想抛出错误,可以使用@Optional,它会把注入参数设置为null

    import { Host } from '@angular/core';
    constructor( @Host heroService: HeroService ) {}
    
    import { Optional } from '@angular/core';
    constructor( @Optional heroService: HeroService ) {}
    

    总结

    angularjs中依赖注入涉及的知识面很多,本文只是作了一个简单介绍,更详细内容参考官方文档和API

    相关文章

      网友评论

          本文标题:浅谈angular2中的依赖注入

          本文链接:https://www.haomeiwen.com/subject/omrikxtx.html