【分享】控制反转 IOC ---- in angular

面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,提高扩展性。

在服务中注入服务

控制反转 IOC

Inversion of Control

用自己的话来说就是,对象的依赖,不再由自己创建(不用自己new,以及挂载等等操作)

而是自己声明一下依赖就行

对象会有一个 ioc 容器工作:把声明的依赖 实例化或者引用,挂载到自身... ...

所以 angular 可以对依赖进行 this.xxxService

通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。

也可以说,依赖被注入到对象中。

控制反转是一种思想,依赖注入是一种设计模式

实现:

(1)依赖查找(是一种更加传统的 IOC 实现方式)

依赖容器和标准 api 实现,容器中的受控对象通过容器的 API 来查找自己所依赖的资源和协作对象

缺点: 无法在容器外使用和测试对象

  • 依赖拖拽

查找的过程是 从集中注册表中 进行的

  • 上下文依赖查找

查找的过程是在 容器管理的资源中 进行的

(2)依赖注入 DI Dependency-injection-in-JavaScript

栗子01:

  • console.log('\n******************** test ********************');
    
    (function() {
    // 球队信息
        class RocketTeam {
            public name: string;
    
            constructor() {
                this.name = '火箭'
            }
        }
    // 球员信息
        class Player {
            public team: RocketTeam;
    
            constructor() {
                this.team = new RocketTeam()
            }
            getTeamInfo() {
                console.log(this.team.name)
            }
        }
    
        let ym = new Player();
        ym.getTeamInfo(); // 火箭
    })();
  • 可扩展性是很差

假如这个时候, 球员发生交易了,球队信息更换了,转换到 FireTeam了。

这时候我们就需要去修改 Player 里的代码了,因为 Player 那里直接写死了对 RocketTeam 的依赖

重新思考下依赖关系处理:

  • 球员和球队之间非得这么直接粗暴的发生联系吗?

一个球员对应一个球队的话,未来会发生变化的可能性太大了,毕竟不止一个球队。

  • 如果两者之间不直接发生联系,中间就需要一个中间模块来负责两者关系的处理

哪支球队控制权应该直接落在 Player 这里了,这正是 IOC 的设计思路

根据 IOC 原则 进行改进(目的: 降低耦合,提高扩展性)

(1)高层模块不应该依赖低层模块

这里 Player 是高层模块,直接依赖了 RocketTeam 这个低级模块。

所以我们将两者解耦,Player不再直接依赖于该 RocketTeam 这个 class

(2)抽象不应该依赖具体实现,具体实现应该依赖抽象

Player 模块不应该直接依赖具体 RocketTeam,而是通过构造函数将抽象的 TeamInfo 实例传递进去,这样就解耦具体实现

(3)面向接口编程,而非面向实现编程

  • console.log('\n******************** test ********************');
    
    (function() {
    // 球队信息
        class TeamInfo {
            public name: string;
    
            constructor(name: string) {
                this.name = name
            }
        }
    // 球员信息
        class Player {
            public team: TeamInfo;
    
            constructor(team: TeamInfo) {
                this.team = team
            }
            getTeamInfo() {
                console.log(this.team.name)
            }
        }
    
        // 将依赖关系放到此处来管理,控制权也放到此处
        // Player 和 TeamInfo 之间不再有直接依赖:Player 不再直接依赖掌握 TeamInfo 的控制权
        // 将依赖控制,落在此处(第三方模块专门管理)即为控制反转
        let ym = new Player(new TeamInfo('火箭'));
        ym.getTeamInfo();
    
        const kobe = new Player(new TeamInfo('湖人'));
        kobe.getTeamInfo();
    })();

    这样再增加一个 team3,改动也不大,复用就行了。

 

angular 依赖注入

  • 将应用逻辑分解为服务, 让组件更加直观的使用,而无须关心实现
  • 提高了模块化程度
  • 解耦,增强了扩展性
  • 代码复用

什么是依赖注入?

就是一个对象 a,依赖于一个对象 b

假设很多个对象,都依赖 b 对象,为了代码复用以及扩展性,把 b 抽象为一个 class B

class B 需要实例化成对象,才能被使用

依赖注入解决的就是:

1. 在对象 a 里面,如何实例化 依赖class B

2. 对象 a 什么时候实例化 依赖class B

3. 对象 a 如何实例化 依赖 B C D 这样的多依赖

4. 以及依赖的实现是单例还是共享

体验:

  • 如果有 angular DI 你会看到:

constructor(private http: HttpClient) 这样的代码

  • 假如没有Angular DI 机制,我们必须手动提供 HttpClient 来创建我们自己的服务

我们的代码会像这样:

我们需要获得 httpClient 对象

于是,我需要再实例一个 const httpClient = new HttpClient(httpHandler);

但 httpHandler 又从哪来?

然后 const myService = new MyService(httpClient);

如果这样创建下去,到底什么时候是个头。

而且,这个过程相当繁琐,而且很容易出错。

Angular 的 DI 机制自动地帮我们完成了上述的所有操作,我们所要做的只是在组件的构造函数中指定依赖项,组件将会很轻松地就能用到这些依赖

基本概念:

(1)注入器 injector

会创建依赖的实例

单例模式: 某个服务,在注入器中最多只有一个实例

不用自己创建注入器: Angular 会在启动过程中为你创建 全应用级注入器 以及所需的其它注入器

  • 当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。
    如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供者来制作一个,并把它加入注入器中,然后把该服务返回给 Angular

(2)装饰器 @Injectable()

可以被注入 依赖+ === 类可以被一个注入器实例化;

Angular 官方: @Injectable() 对所有服务都是必须的

(3)提供商 Provider

  • @Component({
        //...
        // 在组件中配置注入器
        providers: [
            MyService
        ]
        //...
    })

配置注入器(告诉注入器 如何初始化 令牌(Token)所对应的依赖服务

把令牌 (Token) 映射到工厂方法,依赖的实例对象就是通过这个方法创建的

老语法:在Angular 6 发布以前, 唯一的方法是在 providers: [] 中指定服务

 

根据具体使用场景, providers: [] 将有三种不同的用法:

(1)预加载的模块的 @NgModule 装饰器中指定 providers: []

在这种情况下,服务将是全局单例的。

即使它被多个模块的 providers: [] 重复申明,它也不会重新创建实例。

注入器只会创建一个实例,这是因为它们最终都会注册到根级注入器

(2)懒加载的模块的 @NgModule 装饰器中指定 providers: []

在应用程序运行初始化后一段时间,懒加载模块中提供的服务实例才会在子注入器(懒加载模块)上创建。

如果在预加载模块中注入这些服务,将会报 No provider for MyService! 错误。

(3)在@Component和@Directive中使用providers: []

服务是按组件实例化的,并且可以在组件及其子树中的所有子组件中访问。

在这种情况下,服务不是单例的,次我们在另一个组件的模板中使用组件时,我们都会获得所提供服务的新实例

这也意味着服务实例将与组件一起销毁......

providedIn: 'root' 能在 @Component和 @Directive中使用吗? 不能,必须使用 provides 来为每个组件创建多服务对象

 

新语法:providedIn: 'root'

一个服务,只有被注入到某些组件或其他服务, 该服务才会打包服务代码。

缺点:

无助于我们定制服务

优势:

  • 不需要在模块中导入这些服务
  • 我们要做的仅仅是使用它们

在 providedIn 出现之前,公共服务

实现:需要在主模块的 providers: [] 中注入

代价:导致所有(可能的大量)的服务导入进该组件,即使我们只想使用其中一个服务

懒加载 providedIn: 'root' 解决方案:

如果我们在懒加载中使用 providedIn: 'root' 来实现服务会发生什么?

从技术上讲,'root'代表 AppModule ,但 Angular 足够聪明,如果该服务只是在惰性组件/服务中注入,那么它只会绑定在延迟加载的 bundle 中

如果我们又额外将服务注入到其他正常加载的模块中,那么该服务会自动绑定到 mian 的 bundle 中。

  • 1、如果服务仅被注入到懒加载模块,它将捆绑在懒加载包中
  • 2、如果服务又被注入到正常模块中,它将捆绑在主包中

代价:在拥有大量模块和数百项服务的大型应用程序中,它可能变得非常不可预测。

优化:加强模块的边界

(4)四种依赖注入方式

  • 类 的依赖注入

服务:

  • import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class HeroMessageService {
    
        heroMessages: string[] = [
            'root service'
        ];
    
        constructor() { }
    }

组件:(注入根组件,引入 全局服务 单例对象)

  • import { Component, OnInit } from '@angular/core';
    import {HeroMessageService} from '../services/hero-message/hero-message.service';
    
    @Component({
      selector: 'kjf-class-inject',
      templateUrl: './class-inject.component.html',
      styleUrls: ['./class-inject.component.scss']
    })
    export class ClassInjectComponent implements OnInit {
    
      constructor(public messageService: HeroMessageService) {}
    
      ngOnInit(): void {}
    
    }
  • 值 的依赖注入 useValue
  • 别名依赖 的依赖注入 useExisting
  • 限定方式 的依赖注入
  • @Optional() logger: LoggerService 可以兼容依赖不存在的情况,提高系统的健壮性
  • @Host() logger: LoggerService 在父组件中查找依赖,父组件没有这个依赖,则报错

服务:

  • import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class HeroMessageService {
    
        heroMessages: string[] = [
            'root service'
        ];
    
        constructor() { }
    
        // 往缓存中添加一条消息
        addHeroMessage(heroMessage: string) {
            this.heroMessages.push(heroMessage);
            setTimeout(() => this.clearHeroMessages(), 15000);
        }
    
        // 清空缓存
        clearHeroMessages() {
            this.heroMessages = [];
        }
    }

组件:(即使新语法 providedIn:"root" 了,但是在组件中 provides 使得 HeroMessageService 并且将被作为组件级别单例提供)

  • import { Component, OnInit } from '@angular/core';
    import { HeroMessageService } from '../services/hero-message/hero-message.service';
    
    @Component({
        selector: 'kjf-limit-inject',
        templateUrl: './limit-inject.component.html',
        styleUrls: ['./limit-inject.component.scss'],
        providers: [HeroMessageService]
    })
    export class LimitInjectComponent implements OnInit {
    
        constructor(public messageService: HeroMessageService) {}
    
        ngOnInit(): void {}
    
        addMsg(event: Event) {
            return this.messageService.addHeroMessage(`
            '希望把服务的有效性限制到应用程序的一个特定区域,使用:',
            'providers: [HeroService]',
            '通过把服务添加到子组件 @Component() 装饰器的 providers 数组中,来为 HeroesBaseComponent 提供另一个 HeroService 实例',
            '当 Angular 新建 HeroBaseComponent 的时候,它会同时新建一个 HeroService 实例,该实例只在该组件及其子组件(如果有)中可见',
            `);
        }
    }

注意:

(1)Angular注入器是冒泡机制的

  • 当一个组件申请获得一个依赖时,Angular先尝试用该组件自己的注入器来满足它
  • 如果该组件的注入器没有找到对应的提供商,它就把这个申请 转给它的父组件注入器来处理
  • 如果它的父组件注入器也无法满足这个申请,它就继续转给它在注入器树中的父注入器
  • 这个申请继续往上冒泡—直到 Angular 找到一个能处理此申请的注入器 或者 超出了组件树中的祖先位置为止
  • 如果超出了组件树中的祖先还未找到,Angular 就会抛出一个错误

 

疑问:

1. 数据存到 service 感觉已经很好用了,但是 store 的存在,所以在想: 什么场景下,需要用 store 代替 service 存储状态数据

主要是处理了回调的问题,如果是 service,需要传 callBack

如果是 store 则可以使用 rxjs 那样观察订阅的方式

 

鸣谢:

https://segmentfault.com/a/1190000019500553 达观数据文章,让我收获颇多

https://www.juyifx.cn/article/678531135.html

https://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript

https://juejin.im/post/6844903698376720398

https://www.jianshu.com/p/1159cdddde11

https://www.jianshu.com/p/4b10948d456c

https://angular.cn/api/core/Injectable#providedIn

 

posted @ 2020-11-06 11:14  耶梦加德  阅读(375)  评论(0编辑  收藏  举报