依存性注入(DI)
概要
依存性注入は、コードがその依存性をどのように保持するかを取り扱うソフトウェアデザインパターンです。
DIについての詳細については、WikipediaのDependency Injectionを参照してください。 Martin FowlerによるInversion of Control、 またはDIについて書かれた好みのソフトウェアデザインパターンの本を読んでみてください。
DIを一言で説明すると
オブジェクトまたは関数が、依存性を取得するには3つだけ方法が存在します。
- 依存性は、一般的にnew演算子を使用して作成することができます。
- 依存性は、グローバル変数を参照することで調べることができます。
- 依存性は、それが必要な場所に渡すことができます。
依存性を作成する・参照の最初の2つは、依存性をハードコードすることになるため望ましいものではありません。 不可能ではないかもしれませんが、依存性の変更を難しくしてしまいます。 独立したテストのためのモックの依存性を提供することが望ましいケースの際に、そのテストでこれが特に問題になります。
コンポーネントから依存性を発見する義務が取り除かれるため、3つ目が最も現実的です。 依存性は、単純にコンポーネントに引き渡されます。
function SomeClass(greeter) {
this.greeter = greeter;
}
SomeClass.prototype.doSomething = function(name) {
this.greeter.greet(name);
}
上記の例では、SomeClassはgreeterと依存関係が無く、 単純にgreeter実行時に引き渡されます。
これは望ましいことですが、依存性の取得をSomeClassコンストラクタのコードに請け負わせることになります。
依存性作成の請負を管理するために、Angularアプリケーションはインジェクター(injector)を持ちます。 インジェクターは、依存性の調査とコンスタラクタを請け負うサービスロケーターです。
下記は、インジェクターサービスを使用した例です。
// モジュールの設計情報を提供
angular.module('myModule', []).
// インジェクターにどのように'greeter'を構築するか伝える
// greeter自身は、'$window'に依存していることに注意
factory('greeter', function($window) {
// これはファクトリー関数で、'greet'サービス作成を請け負う
return {
greet: function(text) {
$window.alert(text);
}
};
});
// モジュールから、新しいインジェクターが作成される。
// (通常、Angularの起動によって自動的に実行される)
var injector = angular.injector(['myModule', 'ng']);
// インジェクターから、任意の依存性を要求
var greeter = injector.get('greeter');
依存関係を求めることは、ハードコーディングの問題を解決しますが、 それはまた、インジェクターはアプリケーション全体に渡される必要があることを意味します。 インジェクターを渡すということは、デメテルの法則を破るということです。 これを改善するために、この例のように依存関係を宣言することで、インジェクターに依存性の探索を請け負わせます。
<!-- 与えられているHTML -->
<div ng-controller="MyController">
<button ng-click="sayHello()">Hello</button>
</div>
// 定義されているコントローラー
function MyController($scope, greeter) {
$scope.sayHello = function() {
greeter.greet('Hello World');
};
}
// 'ng-controller'ディレクティブは、裏側でこれを行います。
injector.instantiate(MyController);
ng-controllerはクラスをインスタンス化することによって、コントローラーがインジェクターのことを全く知らなくても、 MyControllerの全ての依存性を満たすことが出来ることに注意してください。 これが最善の方法です。 アプリケーションのコードは、インジェクターを扱うことなく、単純に必要とする依存性を要求します。 この構成であれば、デメテルの法則を破ることはありません。
依存性についてのアノテーション(注釈)
インジェクターはどのようにして、何のサービスが注入される必要があるかを知るのでしょうか?
アプリケーション開発者は、インジェクターが依存関係を解決するためのアノテーション(注釈)情報を提供する必要があります。 Angularを通じて、特定のAPI関数はAPIドキュメントに従ってインジェクターを使用することで呼び出されます。 インジェクターは、何のサービスを関数に注入するかを知っている必要があります。 下記は、同様にコードにサービス名情報を注釈する3つの方法です。 これらは好みに合わせて取り替え可能です。
依存関係の推察(Inferring Dependencies)
これは依存関係を取り入れる最も単純な方法で、関数のパラメーター名を依存関係の名前とみなします。
function MyController($scope, greeter) {
...
}
関数を与えられたインジェクターは、関数宣言を調べ、パラメータ名を抽出することによって
注入するサービスの名前を推察することが出来ます。
上記例の$scope
とgreeter
は、関数に注入する必要のある2つのサービスです。
このメソッドでは、パラメーター名を変更するJavaScriptの圧縮/難読化を行うと動作しなくなることは明確です。 このアノテーションの方法は、プロトタイプやデモ用のアプリケーションでのみ有用です。
$injectアノテーション($inject Annotation)
圧縮/難読化による関数パラメータの名前変更と、サービスが正しく関数に注入されるようにするために、 $injectプロパティを使ったアノテーションを行う必要があります。 $injectプロパティは、注入するサービス名の配列になります。
var MyController = function(renamed$scope, renamedGreeter) {
...
}
MyController.$inject = ['$scope', 'greeter'];
この$inject
配列の値の順序は、注入する引数の順序と一致する必要があります。
上記のコードの例では、$scope
はrenamed$scope
に、greeter
は、
renamedGreeter
にそれぞれ注入されます。
$injectアノテーションで最も気をつけなければいけないことは、関数宣言の実際の引数との同期を保持しなければいけないことです。
このアノテーション方法はアノテーション情報を関数に割り当てるため、コントローラー宣言で便利です。
インラインアノテーション(Inline Annotation)
$injectアノテーション形式は、ディレクティブにアノテーションする場合に不便な事があります。
例えば、
someModule.factory('greeter', function($window) {
...
});
結果、一時変数が必要となりコード量が増えてしまいます。
var greeterFactory = function(renamed$window) {
...
};
greeterFactory.$inject = ['$window'];
someModule.factory('greeter', greeterFactory);
このような理由から、3つ目のアノテーション形式は次のように提供されます。
someModule.factory('greeter', ['$window', function(renamed$window) {
...
}]);
全てのアノテーション形式は同等に動作し、注入がサポートされるAngular内のどこででも使用可能な事を覚えておいてください。
DIはどこで使用できるのか?
DIはAngularを通して普及しています。 一般的には、コントローラーとファクトリーメソッドで使用されます。
コントローラー内でのDI
コントローラーはアプリケーションの振る舞いを請け負うクラスです。 コントローラー宣言のお勧めの方法は、配列を使用した書き方です。
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
...
$scope.aMethod = function() {
...
}
...
}]);
こうすることで、コントローラーがグローパル関数化される事を避け、またJavaScript圧縮化への対処にもなります。
ファクトリーメソッド
ファクトリーメソッドは、Angular内のほとんどのオブジェクトの作成を請け負います。 例えば、ディレクティブ、サービス、フィルターが該当します。 ファクトリーメソッドはモジュールに登録され、推奨されるファクトリーの宣言方法は下記のとおりです。
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) {
...
}]);
© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.
このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。
- AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
- "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。