依赖注入 | Dependency Injection

原文链接: Angular Dependency Injection
翻译人员: 铁锚
翻译时间: 2014年02月10日
说明: 译者认为,本文中所有名词性的"依赖" 都可以理解为 "需要使用的资源".

Dependency Injection (DI,依赖注入)是一种软件设计模式,用于处理如何让程序获得其依赖(对象的)引用。

关于 DI 的深入讨论,请参考 维基百科中 Dependency Injection, Martin Fowler 编写的 控制反转(Inversion of Control),或者阅读关于 DI 设计模式方面的书籍/资料.

依赖注入简述 | DI in a nutshell

对象或者函数只有以下3种获取其依赖(的对象)引用的方式:

  1. 依赖可以被使用者自己创建,通过 new 操作符.
  2. 依赖可以通过全局变量(如 window)来查找并引用
  3. 依赖可以在需要的地方被传入

前两种创建或查找依赖的方式并不是最优的,因为他们对依赖进行了硬编码. 这就使得当依赖变得不可用时,要修改依赖相关的代码变得非常困难和繁琐。在测试中更是有问题,因为通常需要通过模拟依赖来进行隔离测试。
第三种选择可以说是最可行的方式,因为它从组件中消除了查找依赖位置的责任。只需要简单地将依赖传递给组件即可。

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

在上面的例子中, SomeClass 不需要关心greeter在哪里, 只需要在运行时有人(调用者)将 greeter 传递给他即可。
这是可取的,但是它将获取依赖的责任交给了构造 SomeClass 的代码。
要管理依赖创建的责任,每个 Angular 应用程序都有一个 injector 。该injector是一个 service locator(定位器) ,负责创建并查找依赖。
下面是一个使用 injector服务的示例:

// 在一个模块中提供连接信息
angular.module('myModule', []).

  // 告诉 injector 如何去构建一个 'greeter'
  // 注意, greeter 自身是依赖于 '$window' 的
  factory('greeter', function($window) {
    // 这是一个 factory function,
    // 职责是为创建  'greet' 服务.
    return {
      greet: function(text) {
        $window.alert(text);
      }
    };
  });

// 新的 injector 从  module 创建.
// (这通常由 angular bootstrap 自动创建)
var injector = angular.injector(['myModule', 'ng']);

// 从 injector 获取所有依赖
var greeter = injector.get('greeter');

要解决依赖关系硬编码的问题,也就意味着 injector 需要贯穿整个应用程序生命周期。传递 injector 打破了 得墨忒耳定律(Law of Demeter, 最少知识原则)。为了弥补这一点,在下面的例子中,我们通过依赖声明的方式将查找依赖的职责交给了 injector:

HTML代码:

<!-- Given this HTML -->
<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>

Angular代码

// 这是 controller 定义
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

// 由 'ng-controller' directive 在后台执行
injector.instantiate(MyController);

注意通过 ng-controller实例化此类,它可以 在 controller 不知道有 injector 的情况下满足MyController 所有的依赖。这是最好的结果。应用程序代码简单地要求所需的依赖项,无需和 injector 打交道。这个设置不违背 得墨忒耳定律。

依赖注解 | Dependency Annotation

injector 怎么知道需要注入何种 service 呢?

为了解决依赖关系,应用程序开发者需要提供 injector 需要的 annotation 信息。在 Angular 中,某些API函数通过使用 injector 来调用,请按照API文档。injector 需要知道注入哪些服务给函数。下面是通过 service name 信息对代码进行注解的三种等价方式。他们都是等价的,你可以在适当的地方互换使用.

推断依赖关系 | Inferring Dependencies

最简单的获取依赖的方式,就是让函数参数名和依赖的名字一致。

function MyController($scope, greeter) {
  ...
}

给定一个 function, injector 通过检查函数声明和提取参数名称可以推断出 service 的名称 。在上面的例子中, $scope 和 greeter 是需要注入 function 的两个 services。

虽然简单直接, 但这种方法在 JavaScript 压缩/混淆 时会失效,因为会重命名方法的参数名。这使得这种注解方式只适用于 pretotyping, 或者 demo 程序中。

$inject 注解 | $inject Annotation

为了可以在压缩代码后依然可以注入正确的 services, 函数需要通过 $inject 属性来注解. $inject 属性是一个数组,包含 需要注入的 service 名字.

var MyController = function(renamed$scope, renamedGreeter) {
  ...
}
MyController['$inject'] = ['$scope', 'greeter'];

在这种情况下,$inject数组中的值的顺序必须和要注入的参数的顺序一致。使用上面的代码片段作为一个例子, '$scope' 将注入到 “renamed$scope”, 而“greeter” 将注入到 “renamedGreeter”。再次提醒注意 $inject 注解必须和 函数声明时的实际参数保持同步(顺序,个数...)。

对于 controller 声明,这种注解方法是很有用的,因为它将注解信息赋给了 function。

内联注解 | Inline Annotation

有时候并不方便使用 $inject 注解,比如在注解 directives的时候。
比如下面的示例:

someModule.factory('greeter', function($window) {
  ...
});

因为需要使用临时变量,导致了代码膨胀为:

var greeterFactory = function(renamed$window) {
  ...
};
greeterFactory.$inject = ['$window'];
someModule.factory('greeter', greeterFactory);

这也是提供第三种注解方式的原因.

someModule.factory('greeter', ['$window', function(renamed$window) {
  ...
}]);

记住,所有的 annotation 风格都是等价的,在 Angular 中,只有支持注入的地方都可以使用.

什么地方应该使用DI | Where can I use DI?

DI在 Angular 中无处不在。它通常用于 controllers 和工厂方法。

控制器中使用DI | DI in controllers

Controllers 类负责应用程序的行为。声明 controllers 的推荐的方法是使用数组表示法:

someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  ...
  $scope.aMethod = function() {
    ...
  }
  ...
}]);

这避免了为 controllers 创建全局函数,并且在代码压缩时继续可用.

工厂方法 | Factory methods

工厂方法在 Angular 中负责创建大多数的对象。例子是 directives, services, 以及 filters。工厂方法被注册到模块, 声明工厂的推荐方法是:

angular.module('myModule', []).
  config(['depProvider', function(depProvider){
    ...
  }]).
  factory('serviceId', ['depService', function(depService) {
    ...
  }]).
  directive('directiveName', ['depService', function(depService) {
    ...
  }]).
  filter('filterName', ['depService', function(depService) {
    ...
  }]).
  run(['depService', function(depService) {
    ...
  }]);

AngularJS中的依赖注入的更多相关文章

  1. 转: 理解AngularJS中的依赖注入

    理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS依赖注入系统是如何运行的. Prov ...

  2. 理解AngularJS中的依赖注入

    点击查看AngularJS系列目录 理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS ...

  3. 详解AngularJS中的依赖注入

    点击查看AngularJS系列目录 依赖注入 一般来说,一个对象只能通过三种方法来得到它的依赖项目: 我们可以在对象内部创建依赖项目 我们可以将依赖作为一个全局变量来进行查找或引用 我们可以将依赖传递 ...

  4. 4.了解AngularJS模块和依赖注入

    1.模块和依赖注入概述 1.了解模块 AngularJS模块是一种容器,把代码隔离并组织成简洁,整齐,可复用的块. 模块本身不提供直接的功能:包含其他提供功能的对象的实例:控制器,过滤器,服务,动画 ...

  5. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  6. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  7. ASP.NET Core 在 JSON 文件中配置依赖注入

    前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...

  8. 在.NET Core控制台程序中使用依赖注入

    之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...

  9. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

随机推荐

  1. Node.js C/C++ 插件

    插件 Addons 是动态链接的共享对象.他提供了 C/C++ 类库能力.这些API比较复杂,他包以下几个类库: V8 JavaScript, C++ 类库.用来和 JavaScript 交互,比如创 ...

  2. Spring完全基于Java配置和集成Junit单元测试

    要点: 配置继承WebApplicationInitializer的类作为启动类,相当于配置web.xml文件 使用@Configuration注解一个类,在类中的方式使用@Bean注解,则表名该方法 ...

  3. Markdown语法及SublimeText下使用技巧

    Markdown语法及SublimeText下使用技巧 0.缘起 最近因为一直在学习Sublime Text,所以也就顺便试用了一下ST对Markdown的支持.正好CSDN正在大力宣传新上线的Mar ...

  4. hadoop入门级总结三:hive

    认识hive  Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能,可以将SQL语句转换为MapReduce任务运行  Hive是建立在 ...

  5. Spark核心类:SQLContext和DataFrame

    http://blog.csdn.net/pipisorry/article/details/53320669 pyspark.sql.SQLContext Main entry point for ...

  6. Android support library支持包常用控件介绍(一)

    谷歌官方推出Material Design 设计理念已经有段时间了,为支持更方便的实现Material Design设计效果,官方给出了Android support design library 支 ...

  7. activiti监听器使用

    分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519) activiti使用的时候,通常需要跟业务紧密的结合在一起,有些业 ...

  8. 一张图带你看懂SpriteKit中Update Loop究竟做了神马!

    1首先Scene中只有开始一点时间用来回调其中的update方法 ;] 2然后是Scene中所有动作的模拟 3接下来是上一步完成之后,给你一个机会执行一些代码 4然后是Scene模拟其中的物理世界 5 ...

  9. [线程]Thead 中传参数RuntimeError: thread.__init__() not called

    在写一个多线程类的时候调用报错 RuntimeError: thread.__init__() not called class NotifyTread(threading.Thread): def ...

  10. 手把手图文并茂教你用Android Studio编译FFmpeg库并移植

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52661331 之前曾写过一篇&l ...