DI (Dependency Injection)とは - 第1回

次期Catalystでも使われるかもというDependency Injectionについて説明します。まず、DIとDI Containerは別の概念なのでまずDIから説明します。

DIとは依存性を注入するという設計上のテクニックの一つです。依存性を注入って言われても何それ?という人も多いはずです。「依存性」を「注入」するというのがどういう意味かを理解するためには、まず依存性とは何か、注入とはどういう意味かを理解することが大切です。

では、具体例で説明していきましょう。

AというクラスがBというクラスに依存していたとします。たとえば、仮に以下のような形に依存していたとします。

package A;
use Moose;
use B;

sub set {
   my ($self, $key, $value) = @_;
   my $b = B->new;
   $b->set('A');
}

この場合、Bの実装にAが依存していることになります。要するに、Bのインスタンスが存在しなければ、Aのsetメソッドをテストすることができません。

では、これをDependency Injectionという設計のパターンを使って切り離した場合のコード例をみてみましょう。

package A;
use Moose;

has 'b' => (
  is => 'rw',
);

sub set {
   my ($self, $key, $value) = @_;
   $self->b->set($key, $value);
}

このコードではbというインスタンスへの依存がなくなりました。要するに、AはBというクラスの実装には何一つ依存していません。

では、このBというオブジェクトをAのインスタンスに設定してくれる人は誰になるでしょう。このAというクラスを利用する利用者になります。使うときには、利用者が以下のようにコンストラクタでBのインスタンスを設定します。

A->new(b => B->new);

さて、ここらでDIの説明に戻りましょう。

DIでいう用語の「依存性」とはBであり、依存性を「注入」というのはAに対してBのオブジェクトを設定することをいいます。AからBへの依存関係を設定するのが、AではなくAの利用者にすることで、AからBへの実装の依存を断ち切っています。

ちなみに、DIの方法には幾つか種類があって、上記のConstructorで依存性を注入するのをConstructor Injetionといいます。また、Setterで設定するのがSetter Injectionといいます。

何故DIが重要なのか?

このようにDIは依存関係を断ち切るための設計のテクニックの一つで、なぜ依存関係を断ち切るのが重要なのかは、Testabilityが高まるからです。

たとえば、最初のようにBの実装に依存したコードになっていたとしましょう。仮にBがCache::Memcachedだったとします。こうしたときに、そのクラスのロジックをテストをするのに、Memcached用のサーバーが必要になってしまうなんてことになるかもしれません。ロジックの単体テストするのに、関係のないMemcachedのサーバーを立ち上げたくはないですよね。

このような問題は、オブジェクトすなわちその実装に依存してまうために発生する問題で、DI可能な設計にしていれば、この問題は簡単に解決できます。

何故IoC Containerという名前が初期につけられたか

Inversion of Control(制御の逆転) Containerと言われていたわけですが、何故そのように言われていたかというのを説明しましょう。

制御とは「依存性(ここではB)のルックアップ方法」のことを指します。この流れが逆転するので、昔はIoC Containerと言われたこともありました。

要するに、AがBという依存関係をServiceのLocatorからルックアップするのではなく、ContainerがBという依存関係をルックアップしてAに設定するため、依存関係のルックアップが逆転しているということを言いたかったからでした。

ですが、最近ではIoC(制御の逆転) Containerといっても何の制御が逆転しているのかわかりにくいことから、DI Containerと呼ばれることのほうが多いです。

DIとDI Containerとの関係は

詳細は第2回目に書きますが、簡単に書くとDIは依存関係を解決するための設計テクニックで、DI Containerは依存関係を設定してくれるContainerです。

先ほど、DIでDependencyを解決してくれるのは利用者(X)と書きましたが、X --> A-->Bといったような関係であれば、XがAとBの関係を設定してやればよいでしょう。

では、次のようにAがBにBがCに依存(X --> A --> B --> C)となった場合、BとCの依存を解決するのは誰がいいでしょうか?AがCの依存性を注入して、XがBの依存性を注入すればよいでしょうか?こうすると、AはCに依存し、XはBの実装に依存します。

このように少し考えるとわかるのですが、DIという設計テクニックを使っても、利用者(依存性を注入する側)はその依存性の対象に依存してしまうことになります。この問題を解決してくれるのがDI Containerです。

DIまではやったことがあるけれど、DI Containerを使ったことはないっていう人は多いかもしれないですね。第2回はDI Containerとは何か?を説明します。