• 【分享】控制反转 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('
      ******************** 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('
      ******************** 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

     

  • 相关阅读:
    Spring sprint @ ninth day
    微软面试:找水王问题
    Spring sprint @ first day
    软件工程概论 购书最低价格问题
    网络助手的NABCD分析
    记初学net-SNMP
    求二维数组联通子数组和的最大值 (联通涂色) beta!
    二维数组环状最大子矩阵
    一维数组中的最大子数组(环状)
    图书助手冲刺第七天
  • 原文地址:https://www.cnblogs.com/tianxiaxuange/p/13935872.html
Copyright © 2020-2023  润新知