AngularjSの中心機能として『依存性の注入-DI(Dependency Injection)』というのがあります。私はこれがAngularの最大の個性であり面白い所だと思うのですが、最近調べていくうちにAngularJSの世界の上に住む私たちはDIはほとんど必要ないよねって思い出したので書きました。その理由をDIの説明も含めて書いていきます。
依存性の注入-DI(Dependency Injection)とは?
まず初めに、DIとは何かを説明していきます。DIとは『実装時ではなく、実行時に依存性を注入する』という考えです。
下記コードで具体例を見ていきましょう。
DIでないコード
var GasolineEngine = function(){...};
var Car = function(){
this.engine = new GasolineEngine();
};
var my_car = new Car();
var another_car = new Car();
上記では、MyCarの中で直接new GasolineEngine();
しています。これはCarがGasolineEngineのオブジェクトに依存しており、var my_car = new Car()
をした時点でGasolineEngineの車しかつくれない事を意味します。my_carもanother_carもGasolineEngineの車しかつくれません。
DIなコード
var Engine = function(name){...};
var Car = function(engine_name){
this.engine = new Engine(engine_name);
};
var my_car = new Car("Gasoline");
var another_car = new Car("Motor");
こちらでは、Carの引数にengine_nameを渡して、new Engine(engine_name)
しています。つまり、CarクラスだけではEngineの種類はわからず、new Car("Gasoline")
という宣言時に初めてエンジンの種類がわかるようになっています。これが『依存性の注入-DI(Dependency Injection)』です。(同時にこれを宣言型プログラミングといいます)
※補足
宣言型プログラミングについては下記の説明が参考になります。
Swiftで脱アルゴリズム!iOS開発を関数型(宣言型)プログラミングへパラダイムシフトしてみる【脱アルゴリズム宣言①】2014-06-01
AngularJSで使われているDIとは?
AngularJSのDIを説明する前に、まずはAnnotationについて説明する必要があります。AngularjSのDIとAnnotationは切っても切り離せない関係にあり、Annotationを理解することでDIについても理解することができます。
AngualrJSのAnnotationについて
annotationの意味
注釈, 注記
下記コードを見てみましょう。良く見るAngularJSのコードだと思います。
var app = angular.module("myApp");
app.controller("myCtrl", ["$scope", function($scope){
...
}]);
実はこれ、以下のようなコードでも動かすことは出来ます。
var app = angular.module("myApp");
app.controller("myCtrl", function($scope){
...
});
しかし、angular_without_annotation.js
ではソースコードのminify化などをした際に動かなくなるという問題が発生します。AnguarJS内には$scope
というオブジェクトがあるのですが、functionの引数はminify化によってfunction($scope) -> function(r)
のように変換されてしまうため引数が$scope
というクラスという事を判別できません。そこでAngularJSではannotationという機能を使って1つ目の引数が$scope
クラスであるということを教えています。それがangular_wiht_annotation.js
の["$scope", ..]
です。
AngularJSではminify対策としてannotationを使っているのです。
DIとAnnotatinの関係
さて、実はここまでのコードで全て説明出来るのですが、AngularJSではcontrollerが$scopeに持つことをDIで実現しています。
var app = angular.module("myApp");
app.controller("myCtrl", ["$scope", function($scope){
...
}]);
car_with_di.jsとangular_with_annotation.jsの説明で少しだけ違うのはfunctionに渡される引数が文字列かオブジェクトかという部分です。car_with_di.jsでは"Gasoline"という引数を渡していました。しかし、angular_with_annotation.jsではcontrollerの初期処理で"$scope"
という文字列から$scopeクラスを判定し、functionの引数にそのオブジェクトを渡しています。
なので個人的には、Angularjsとして下記のような設計も可能だったとは思います。
var app = angular.module("myApp");
app.controller("myCtrl", function(){
scope = new $scope();
...
});
しかし、AngularjsではDIを使ってcontroller作成実行時に$scopeの依存性を注入する選択肢をとったのでこういうコードにはならないのです。
(詳しくは更に色々理由があるのですが、今回は省きます。)
AngularjsのDIについては下記も参考にあるので載せておきます。
AngularJSでよく見るDependency Injectionについて 14-09-21
Angularjs上の私たちのコードがDIじゃなくても良い理由
DIとはオブジェクト指向の設計方針「実装ではなくインターフェースに対してプログラミングすべし」という事を実現するために存在しています。そして、インターフェースをつくる時は多くの場合共通した処理を同じ形式で呼び出したい時です。
例えば、以下のコードを見てみましょう。
ここだけは筆者のJavascript力が足りない事もあり、下記ブログから注釈させていただきました。
http://promamo.com/?p=2241
※どなたかJavascriptコードでかける方が入ればご助言お願いします。その際は後で編集して下記をそちらに変更したいと思います。
interface Animal {
public void makeSound();
}
class Dog implements Animal {
public void makeSound(){
bark();
}
private void bark(){
//犬の吠え声
System.out.println('woof!woof!');
}
}
// 実装に対するプログラミング
Dog dog = new Dog();
dog.bark();
//インターフェースに対するプログラミング
Animal animal = new Dog();
animal.makeSound();
このmakeSound()
はAnimal全体で共通するインターフェースです。しかし、本当に我々はmakeSound()
でdogを呼び出したいのでしょうか?我々がユーザーに向けたコードを書くときは大抵の場合具体的な事を書くと思います。「犬の鳴き声」と伝えるか、「動物のつくる音」と伝えるかでユーザーの理解度はかなり変わると思いませんか?我々はユーザーに理解してもらえるようにコードを書くため、できるだけ具体的に「犬の鳴き声」と書くことが多いでしょう。
もちろん、「犬という動物のつくる音」というような形で補足説明的に伝える事も出来ますが、そんな説明の煩わしいことに時間的コストをかけることはビジネス上選ばないでしょう。
さて、それでも我々がDIをコーディングする必要があるでしょうか?例えば下記のようなangular.jsの2つのコードです。
var app = angular.module("myApp");
app.controller("myCtrl", ["$scope", function($scope){
$scope.save = function(price){
var engine = new GasolineEngine();
engine.price = price;
engine.$save();
}
...
});
var app = angular.module("myApp");
app.controller("myCtrl", ["$scope", function($scope){
$scope.save = function(price, name){
var engine = new Engine(name);
engine.price = price;
engine.$save();
}
...
});
ex2.jsでは複数のエンジンを切り替える処理があることを想定しています。さて、みなさんは最初からGasolineEngineとMotorEngineがあることを想定してつくりますか?多くの場合つくらないでしょう。なぜなら要求にMotorEngineも想定しろというのがないからです。そんな状況でインターフェースをつくるのは手間だと思うでしょう。
更に言うと、どんなエンジンがあるかわかってない状況でインターフェースをつくってもインターフェース自体に間違いが発生する確率が高くなります。DIでもインターフェースの変更は決してお手軽ではありません。また間違ったインターフェースは逆に見通しの良くないコードの原因にもなります。
我々はex1.jsの書き方を選ぶでしょう。これがAngularJSの中身はDIだけど、その上にのる私たちのコードはDIじゃなくても良いよねとタイトルで書いた理由です。
最後に
とは言っても、実はDI自体はすごく良いデザインパターンだと考えています。上記も開発当初はDIじゃなくても良いですが、経験値を得てどんなインターフェースが良いか見えて来ればDIに変更していくのも良いと思います。ただ、基本的に我々がそれを享受する価値を得るのはそのコードを書いてから半年以上先だと考えても良いでしょう。
Angularjsでは多くの我々に使ってもらいやすくするためにDIを選んでいるのかもしれません。我々の多様な要求を満たすためにはDIは確かに良い選択肢だと思います。
長々と書きましたが、Angularjsに触れることで多くのことを学ぶことが出来ました。まだまだ学べそうなので、もうしばらくはAngularjsと共に生きていく予定です。
当記事が皆さんの学びや考察に役立てば良いなと思います。ご覧いただきありがとうございました。何かご意見等ある場合はぜひコメントにてお願いします。(きっとあるはず・・・っ!w)
当記事を書く上で参考にしたサイトまとめ
Swiftで脱アルゴリズム!iOS開発を関数型(宣言型)プログラミングへパラダイムシフトしてみる【脱アルゴリズム宣言①】2014-06-01
AngularJSでよく見るDependency Injectionについて 14-09-21
http://promamo.com/?p=2241
http://www.informit.com/articles/article.aspx?p=2245729
http://dann.g.hatena.ne.jp/dann/20080911/p1
http://qiita.com/kawaz/items/363f430d21ec729f1b7d
ng-annotateって何?
AngularJSリファレンス
DIってのは硬直した言語のための技術なんだと気がついた。2009-10-03
Ruby用DIコンテナDeeをつくった-2014-01-01
http://qiita.com/armorik83/items/b00818ecaf2e93734b36
http://qiita.com/hshimo/items/1136087e1c6e5c5b0d9f
http://en.wikipedia.org/wiki/AngularJS
http://merrickchristensen.com/articles/javascript-dependency-injection.html
https://github.com/angular/angular.js/blob/master/src/auto/injector.js
https://docs.angularjs.org/guide/di
http://dann.g.hatena.ne.jp/dann/20080911/p1
https://github.com/angular/angular.js/blob/aa268560064e5875bd471da3f7d1ebc2f9e6b3b7/src/auto/injector.js#L64-L116
http://d.hatena.ne.jp/kitokitoki/20110605/p1
http://wazanova.jp/post/64057760114/angularjs-google-i-o-2013
http://blogs.wankuma.com/nagise/archive/2007/08/03/88554.aspx
http://promamo.com/?p=2241