依赖注入 | Dependency Injection
说明 : 译者认为,本文中所有名词性的" 依赖 " 都可以理解为 " 需要使用的资源 ".
Dependency Injection (DI,依赖注入)是一种软件设计模式,用于处理如何让程序获得其依赖(对象的)引用。
关于 DI 的深入讨论,请参考 维基百科中 Dependency Injection, Martin Fowler 编写的 控制反转(Inversion of Control),或者阅读关于 DI 设计模式方面的书籍/资料.
依赖注入简述 | DI in a nutshell
对象或者函数只有以下3种获取其依赖(的对象)引用的方式:- 依赖可以被使用者自己创建,通过 new 操作符.
- 依赖可以通过全局变量(如 window)来查找并引用
- 依赖可以在需要的地方被传入
第三种选择可以说是最可行的方式,因为它从组件中消除了查找依赖位置的责任。只需要简单地将依赖传递给组件即可。
- function SomeClass(greeter) {
- this.greeter = greeter;
- }
- SomeClass.prototype.doSomething = function(name) {
- this.greeter.greet(name);
- }
这是可取的,但是它将获取依赖的责任交给了构造 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');
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) {
- ...
- }
虽然简单直接, 但这种方法在 JavaScript 压缩/混淆 时会失效,因为会重命名方法的参数名。这使得这种注解方式只适用于 pretotyping, 或者 demo 程序中。
$inject 注解 | $inject Annotation
为了可以在压缩代码后依然可以注入正确的 services, 函数需要通过 $inject 属性来注解. $inject 属性是一个数组,包含 需要注入的 service 名字.- var MyController = function(renamed$scope, renamedGreeter) {
- ...
- }
- MyController['$inject'] = ['$scope', 'greeter'];
对于 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) {
- ...
- }]);