【问题标题】:Angular Dependency Injection Implementation, Why?Angular 依赖注入实现,为什么?
【发布时间】:2018-04-13 19:04:19
【问题描述】:

如果有人说,请原谅我不理解。

为什么它的依赖注入器的 Angular 实现通过 constructor 使用注入器?

我习惯于以各种方式看到DI。即使是静态方法也有意义(如果存在,抱歉我还没有深入挖掘,到目前为止我已经研究了一周)。

这样使用会不会更容易或更合乎逻辑,更类似于我们经常看到的 DI 但仍然在构造函数中传递它?:

// Non-Angular Example
@Component({})
class FooComponent {
  public appState: AppState;

  constructor(DI: DependencyInjector) {
    this.appState = DI.get('AppState'); 
  }

  ngOnInit() {}
}

Angular 更像是这样,我不确定它是否只是为了冗长,还是有其他原因。

// Angular 2/4 Example 
@Component({})
class BarComponent {
  public appState: AppState;

  constructor(appState: AppState, 
              router: Router,
              etc: EtcSomething) {

  }
  ngOnInit() {}

我知道 Google 已经想到了这一点,我只是想了解其中的原因和/或好处。也许我醒来时想到了一些愚蠢的事情,这很明显,只是我的头晕目眩,但我错过了。

我希望我的问题是有道理的,我只是想知道为什么。

【问题讨论】:

  • 因为那不再是依赖注入了。依赖注入的原理是将依赖注入到组件中。 BarComponent 的依赖项是路由器、appState 等。唯一注入的东西是注入器,它充当工厂。它不再是 DI,并且使事情更难测试。我不确定你在哪里见过一个依赖注入框架,它除了注入器之外不注入任何东西。
  • 您可以只注入注入器,然后使用它手动注入,但这会掩盖依赖关系并使 typescript 编译器的帮助降低,因此您只是无缘无故地自取其辱。
  • 谢谢大家,有人想把其中一个 cmets 作为答案,让我标记一下吗?您能否澄清这是否是 Angular 特定的 DI,而不是一般的 DI?任何语言的通用 DI 处理方式都与我的经验大不相同;但是,这是一种原型语言,因此可能会有所不同。我并不反对它的用途,我试图理解它的目的。 :)
  • @JBNizet 据我了解(尽管可能是错误的),在 Angular 之外,第一个示例仍被视为依赖注入。我认为 Angular 的流行让人们认为所有的 DI 都像 Angular DI。参考:stackoverflow.com/a/130862/846550 以及关于该问题的其他答案。
  • 对于OP的第一个DI示例,优点是不需要对语言特性进行任何修改,也不需要引入神奇的保留参数名称。我能看到的唯一缺点是样板文件略多(与 OP 相比,在 DI 中隐藏更多的样板文件是可能的)。所以我也很好奇为什么 Angular 会这样做;这是个好问题。

标签: angular


【解决方案1】:

AngularJS 中的依赖注入非常有用,也是制作易于测试的组件的关键。本文解释了 Angular 的依赖注入系统是如何工作的。

提供者 ($provide) $provide 服务负责告诉 Angular 如何创建新的可注入事物;这些东西被称为服务。服务由称为提供程序的事物定义,这是您在使用 $provide 时创建的。定义提供程序是通过 $provide 服务上的 provider 方法完成的,您可以通过要求将 $provide 服务注入应用程序的配置函数来获取它。我们可以将一个名为 greeting 的变量注入到任何可注入的函数中(比如控制器,稍后会详细介绍),Angular 会调用提供者的 $get 函数来返回一个新的服务实例。

【讨论】:

    【解决方案2】:

    构造函数注入是实现依赖注入的最简单、最明智的方法。

    即使这对用户来说并不总是最方便,因为它有某些限制,比如不支持循环依赖,超类需要从子类转发所有参数。

    任何其他方式都会让人很难知道依赖项何时可用并且可以访问。 需要在注入依赖项后调用额外的生命周期回调。

    使用继承创建对象非常困难,因此类是从内部(超类)向外(子类)构造的,因此每个级别都有一个正确初始化的状态,其中几个前提条件成立,就像没有子类访问之前的成员一样超类构造函数未完成。 例如,如果超类中有非泛型构造函数,Typescript (ES6) 会强制在构造函数中调用 super()。

    如果子类中存在构造函数,在使用“this”之前需要先调用super()。

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

    【讨论】:

      【解决方案3】:

      这样使用会不会更容易或更合乎逻辑,更类似于我们经常看到的 DI 但仍然在构造函数中传递它?

      您示例中的模式说明实际上称为Service locator pattern。这种模式被许多人认为是一种反模式。

      Service Locator Pattern

      优势

      • “服务定位器”可以充当简单的运行时链接器。这允许在运行时添加代码,而无需重新编译应用程序,在某些情况下甚至无需重新启动应用程序。
      • 应用程序可以在运行时通过选择性地添加和删除服务定位器中的项目来优化自身。例如,应用程序可以检测到它具有比默认库更好的用于读取 JPG 图像的库,并相应地更改注册表。
      • 库或应用程序的大部分部分可以完全分离。它们之间的唯一联系就是注册表。

      缺点

      • 相对于系统的其余部分,注册表中的内容实际上是黑匣子。这使得检测和从错误中恢复变得更加困难,并且可能会降低整个系统的可靠性。
      • 注册表必须是唯一的,这可能使其成为并发应用程序的瓶颈。
      • 注册表可能是一个严重的安全漏洞,因为它允许外部人员将代码注入应用程序。
      • 注册表隐藏了类的依赖关系,当依赖关系缺失时会导致运行时错误而不是编译时错误。
      • 注册表使代码更难维护(与使用依赖注入相反),因为不清楚何时引入重大更改。
      • 注册表使代码更难测试,因为所有测试都需要与同一个全局服务定位器类交互,以设置被测类的虚假依赖项。但是,通过使用单个服务定位器接口注入应用程序类,可以轻松克服这一问题。

      Dependency Injection

      angular(以及其他框架)当前使用的(首选)模式。

      优势

      • 依赖注入允许客户端灵活地进行配置。只有客户端的行为是固定的。客户可以对任何支持客户期望的内在接口的东西采取行动。
      • 依赖注入可用于将系统的配置详细信息外部化到配置文件中,从而无需重新编译即可重新配置系统。可以针对需要不同组件实现的不同情况编写单独的配置。这包括但不限于测试。
      • 因为依赖注入不需要对代码行为进行任何更改,它可以作为重构应用到遗留代码。结果是客户端更加独立,并且更容易使用存根或模拟其他未测试对象的模拟对象进行单独的单元测试。这种易于测试通常是使用依赖注入时注意到的第一个好处。
      • 依赖注入允许客户端删除它需要使用的具体实现的所有知识。这有助于使客户免受设计变更和缺陷的影响。它促进了可重用性、可测试性和可维护性。
      • 减少了应用程序对象中的样板代码,因为初始化或设置依赖项的所有工作都由提供程序组件处理。
      • 依赖注入允许并发或独立开发。两个开发人员可以独立开发相互使用的类,而只需要知道这些类将通过什么接口进行通信。插件通常由第三方商店开发,它们甚至从不与创建使用插件的产品的开发人员交谈。
      • 依赖注入减少了类与其依赖之间的耦合。

      缺点

      • 依赖注入创建的客户端要求构造代码提供配置详细信息。当存在明显的默认值时,这可能会很麻烦。
      • 依赖注入会使代码难以跟踪(读取),因为它将行为与构造分开。这意味着开发人员必须参考更多文件来了解系统的执行情况。
      • 依赖注入通常需要更多的前期开发工作,因为人们无法在需要的时间和地点正确地调用它,但必须要求注入它,然后确保它已被注入。
      • 依赖注入迫使复杂性移出类并进入类之间的链接,这可能并不总是可取或易于管理。
      • 具有讽刺意味的是,依赖注入会鼓励对依赖注入框架的依赖。

      您可以找到有关这些模式的文章、书籍、教程和其他信息。尽管这是一个偏好问题,但人们普遍认为Dependency injection 优于Service locator pattern。

      关于这两个之间的差异还有其他类似的问题,例如:What's the difference between the Dependency Injection and Service Locator patterns?。

      【讨论】:

      • 以下任何一个明确指定的接口,不只是DI吗?:“两个开发人员可以独立开发相互使用的类,而只需要知道这些类将通过什么接口进行通信。 "
      【解决方案4】:
      // Non-Angular Example
      
      @Component({})
      class FooComponent {
        public appState: AppState;
      
        constructor(DI: DependencyInjector) {
          this.appState = DI.get('AppState'); 
        }
      
        ngOnInit() {}
      }
      

      您可以在构造函数中使用 injector 并手动注入您需要的内容:

      @Component({})
      class FooComponent {
        public appState: AppState;
      
        constructor(private injector: Injector) {
          this.appState = injector.get(AppState); 
        }
      
        ngOnInit() {}
      }
      

      但是在这种情况下,执行注入器工作的组件在大多数情况下是不合理的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-23
        • 2011-01-03
        • 2016-02-15
        • 1970-01-01
        相关资源
        最近更新 更多