AngularJS中的依赖注入

依赖注入 | Dependency Injection


说明 : 译者认为,本文中所有名词性的" 依赖 " 都可以理解为 " 需要使用的资源 ".
Dependency Injection (DI,依赖注入)是一种软件设计模式,用于处理如何让程序获得其依赖(对象的)引用。

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

依赖注入简述 | DI in a nutshell

对象或者函数只有以下3种获取其依赖(的对象)引用的方式:
  1. 依赖可以被使用者自己创建,通过 new 操作符.
  2. 依赖可以通过全局变量(如 window)来查找并引用
  3. 依赖可以在需要的地方被传入
前两种创建或查找依赖的方式并不是最优的,因为他们对依赖进行了硬编码. 这就使得当依赖变得不可用时,要修改依赖相关的代码变得非常困难和繁琐。在测试中更是有问题,因为通常需要通过模拟依赖来进行隔离测试。
第三种选择可以说是最可行的方式,因为它从组件中消除了查找依赖位置的责任。只需要简单地将依赖传递给组件即可。
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. function SomeClass(greeter) {  
  2.   this.greeter = greeter;  
  3. }  
  4.    
  5. SomeClass.prototype.doSomething = function(name) {  
  6.   this.greeter.greet(name);  
  7. }  
在上面的例子中,  SomeClass  不需要关心 greeter 在哪里, 只需要在运行时有人(调用者)将 greeter 传递给他即可。
这是可取的,但是它将获取依赖的责任交给了构造 SomeClass 的代码。
要管理依赖创建的责任,每个 Angular 应用程序都有一个  injector  。该injector是一个 service locator(定位器) ,负责创建并查找依赖。
下面是一个使用 injector服务的示例:
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 在一个模块中提供连接信息  
  2. angular.module('myModule', []).  
  3.    
  4.   // 告诉 injector 如何去构建一个 'greeter'  
  5.   // 注意, greeter 自身是依赖于 '$window' 的  
  6.   factory('greeter'function($window) {  
  7.     // 这是一个 factory function,   
  8.     // 职责是为创建  'greet' 服务.  
  9.     return {  
  10.       greet: function(text) {  
  11.         $window.alert(text);  
  12.       }  
  13.     };  
  14.   });  
  15.    
  16. // 新的 injector 从  module 创建.   
  17. // (这通常由 angular bootstrap 自动创建)  
  18. var injector = angular.injector(['myModule''ng']);  
  19.    
  20. // 从 injector 获取所有依赖  
  21. var greeter = injector.get('greeter');  
要解决依赖关系硬编码的问题,也就意味着 injector 需要贯穿整个应用程序生命周期。传递 injector 打破了  得墨忒耳定律 (Law of Demeter, 最少知识原则)。为了弥补这一点,在下面的例子中,我们通过依赖声明的方式将查找依赖的职责交给了 injector:

HTML代码:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <!-- Given this HTML -->  
  2. <div ng-controller="MyController">  
  3.   <button ng-click="sayHello()">Hello</button>  
  4. </div>  

Angular代码
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 这是 controller 定义  
  2. function MyController($scope, greeter) {  
  3.   $scope.sayHello = function() {  
  4.     greeter.greet('Hello World');  
  5.   };  
  6. }  
  7.    
  8. // 由 'ng-controller' directive 在后台执行  
  9. injector.instantiate(MyController);  

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

依赖注解 | Dependency Annotation

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

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

推断依赖关系 | Inferring Dependencies

最简单的获取依赖的方式,就是让函数参数名和依赖的名字一致。
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. function MyController($scope, greeter) {  
  2.   ...  
  3. }  
给定一个 function, injector 通过检查函数声明和提取参数名称可以推断出 service 的名称 。在上面的例子中, $scope 和 greeter 是需要注入 function 的两个 services。

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

$inject 注解 | $inject Annotation

为了可以在压缩代码后依然可以注入正确的 services, 函数需要通过  $inject  属性来注解. $inject 属性是一个数组,包含 需要注入的 service 名字.
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. var MyController = function(renamed$scope, renamedGreeter) {  
  2.   ...  
  3. }  
  4. MyController['$inject'] = ['$scope''greeter'];  
在这种情况下,$inject数组中的值的顺序必须和要注入的参数的顺序一致。使用上面的代码片段作为一个例子, ' $scope ' 将注入到 “ renamed$scope ”, 而“ greeter ” 将注入到 “ renamedGreeter ”。再次提醒注意 $inject 注解必须和 函数声明时的实际参数保持同步(顺序,个数...)。

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

内联注解 | Inline Annotation

有时候并不方便使用 $inject 注解,比如在注解 directives的时候。
比如下面的示例:
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. someModule.factory('greeter'function($window) {  
  2.   ...  
  3. });  
因为需要使用临时变量,导致了代码膨胀为:
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. var greeterFactory = function(renamed$window) {  
  2.   ...  
  3. };  
  4. greeterFactory.$inject = ['$window'];  
  5. someModule.factory('greeter', greeterFactory);  
这也是提供第三种注解方式的原因.
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. someModule.factory('greeter', ['$window'function(renamed$window) {  
  2.   ...  
  3. }]);  

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

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

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

控制器中使用DI | DI in controllers

Controllers 类负责应用程序的行为。声明 controllers 的推荐的方法是使用数组表示法:
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. someModule.controller('MyController', ['$scope''dep1''dep2'function($scope, dep1, dep2) {  
  2.   ...  
  3.   $scope.aMethod = function() {  
  4.     ...  
  5.   }  
  6.   ...  
  7. }]);  

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

工厂方法 | Factory methods

工厂方法在 Angular 中负责创建大多数的对象。例子是 directives, services, 以及 filters。工厂方法被注册到模块, 声明工厂的推荐方法是:
[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. angular.module('myModule', []).  
  2.   config(['depProvider'function(depProvider){  
  3.     ...  
  4.   }]).  
  5.   factory('serviceId', ['depService'function(depService) {  
  6.     ...  
  7.   }]).  
  8.   directive('directiveName', ['depService'function(depService) {  
  9.     ...  
  10.   }]).  
  11.   filter('filterName', ['depService'function(depService) {  
  12.     ...  
  13.   }]).  
  14.   run(['depService'function(depService) {  
  15.     ...  
  16.   }]);  

猜你喜欢

转载自blog.csdn.net/gujinapenggu5/article/details/50791701