依赖注入 | 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. Java中next()和nextLine()

    next()读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键.Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键 ...

  2. 配置 docker0 网桥

    Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络. Docker 默认 ...

  3. CentOS 安装Docker

    CentOS 系列安装 Docker Docker 支持 CentOS6 及以后的版本. CentOS6 对于 CentOS6,可以使用 EPEL 库安装 Docker,命令如下 $ sudo yum ...

  4. Spring Boot+maven打war包

    存在一个坑: 官网文档 指出以下前3步做法,但是这样只可以打出可运行的jar包,要打出war包还要在文档后面的链接跳到另一个页面,才能找到第四步的做法,也就是最终能够打出war包,可能有些朋友有些粗心 ...

  5. Programming In Scala笔记-第八章、函数与闭包

    当程序的代码量增大时,就需要对各功能模块进行分割,这些分割的小模块就是本文中接下来会进行分析的函数.接下来的部分会讲解包括函数嵌套,函数字面量,以及函数值等概念. 一.方法 一会函数一会方法的,是不是 ...

  6. RxJava(11-线程调度Scheduler)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51821940 本文出自:[openXu的博客] 目录: 使用示例 subscribeOn原理 ...

  7. MT8127:如何让system分区可读写(MTK安卓6.0)

    Android 系统默认情况下,system 分区是只读 mount 的,因为无法进行往里写数据的,可 以用 adb 命令 adb remount 重新 mount 一下. 也可以通过在板子上,输入以 ...

  8. Android初级教程:RatingBar的使用

    记得淘宝里面买家给卖家评分的时候会有一个星星状的评分条,其实就是基于RatingBar做了自定义使用了.那么本篇文章就对RatingBar的使用做一个基本的认识. 接下来就是正题,那就是对于Ratin ...

  9. android开发常用工具箱

    我的工具包资料目录 我的个人总结,最近做的项目需要了的一些资料,感觉挺乱的,然后现在整理了一下. Jar包 包名 版本号 作用 下载地址 xUtils 2.6.14和3.1.26 大文件上传下载等 旧 ...

  10. 4-sum问题

    给定一个整数数组,判断能否从中找出4个数a.b.c.d,使得他们的和为0,如果能,请找出所有满足和为0个4个数对. #define SIZE 10 void judgeAndPut(int* arr, ...