原文链接: 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) {
...
}]);

最新文章

  1. django模板里循环变量&lt;table&gt;里想要两个一行如何控制
  2. 如何解救在异步Java代码中已检测的异常
  3. Java 8 Lambda表达式探险
  4. ueditor1.3.6jsp版在struts2应用中上传图片报&quot;未找到上传文件&quot;解决方案
  5. [游戏模版13] Win32 透明贴图 主角移动
  6. Redis word bak
  7. Redirect HTTP to HTTPS on Tomcat
  8. The Brain as a Universal Learning Machine
  9. SNMP SNMP协议
  10. ubuntu samba服务器多用户配置【转】
  11. POJ 2398 Toy Storage(计算几何,叉积判断点和线段的关系)
  12. Java 单链表的倒置
  13. 如何将Android Studio与华为软件开发云代码仓库无缝对接(二)
  14. DB2 执行SQL脚本
  15. Java 嵌套类基础详解
  16. 怎么对MySQL数据库操作大数据?这里有思路
  17. 如何不使用loop循环创建连续的数组
  18. android 使用webview 加载网页
  19. CRC校验的实现
  20. ML_入门

热门文章

  1. TeXLive安装过程
  2. 小希的迷宫(HDU 1272 并查集判断生成树)
  3. Android IntentService 与Alarm开启任务关闭任务
  4. Extjs4 Grid内容已经拿到但是不显示数据
  5. PL/SQL --&gt; 动态SQL调用包中函数或过程
  6. &lt; IOS &gt; IOS适配,简单的分析解决一下
  7. Climbing Stairs 解答
  8. Android中调用C++函数的一个简单Demo
  9. [转]ActiveMQ 即时通讯服务 浅析
  10. &lt;ListView&gt;分列显示