依存性注入とLaravelのIoCクラス(基本)

タグ: Laravel3   依存性注入  

正確さより、わかりやすさで依存性注入を説明します。

でも、どうしても依存性注入を取り入れなくてはならないというわけではないのです。開発規模やスタイルにより決まるのです。もし、自分で使うちょっとしたツールをお手軽に素早く使用する場合は、まず使用する必要はありません。逆に厳密なシステムで、必ずしっかりとしたテストが求められている場合は、依存性注入をしないと、まともに単体テストができないという事態を引き起こします。

依存性注入とは

簡単に定義すれば、クラスを切り替えられるようにする仕組みのことです。仕組みは色々とありますがLaravelではIoCクラスを使用します。

例えばecho 'こんにちは、世界さん'とコーディングしてしまえば、世界の部分を変更できません。しかも、この文章は日本人にしかわからないので、他の国の人に呼んでもらえないページを作成することとなります。

状況に合わせ、全体の文言を英語にしたり、フランス語にしたり、切り替えられるようにコーディングすることができます。また、世界の部分をログインしたユーザー名に置き換えることもよく行われます。状況に合わせて動的に切り替えられるようにするわけですね。

これをクラスで考えましょう。AppleクラスでOrangeクラスを生成しているとします。

class Apple {
    fucntion getDrink() {
        return 'りんごジュース';
    }
    function getMixDrink() {
        $orange = new Orage;
        return $orange->to_string().$this->getDrink();
    }
}

もちろんOrageクラスを使用したいため生成しているのです。でもこれですと、Appleクラスの動作はOrangeクラスの機能に依存してしまっています。そのためAppleクラスの動作だけをテストできないのです。Appleクラスをテストしようと思うと、Orangeクラスの動作を含めたAppleクラスの動作をチェックすることになります。

一つの機能単位をテストすることを単体テストと呼びます。同じ機能単位でもCRUDなどのシナリオに沿って実行するものを機能テストと呼ぶこともあります。複数の機能単位と関係付けてテストすることは一般に結合テストと言います。それ以上のまとまったテストは状況により、全体テストとかシステムテストとか実働テストとか色々言い方はありますが、言語・会社などの環境により、多少言葉が意味する範囲には違いはあります。

昔ながらの多くの人が関わり、上流工程から順番に作成していくフォールダウンで比較的大きなシステムを構築する場合、テストは重要です。しかし、もしあなたが自分用の小さなシステムを作っては確かめを繰り返して作成していくのであれば、細かいテストは目視で行なっているのです。たとえバグがあっても影響範囲は限定的ですから、その分テストの重要性は低くなります。

大きなシステムに主に携わる人達はこうしたテストを重要視し、個人的なシステムや比較的低機能なシステムにも、大きなシステムと同じ基準を押し付けてしまいます。そのため小さなシステムに携わる人達に余計な罪悪感やら、義務感を追わせてしまうため、かえって避けられてしまいます。

もし、小さなシステムを作成するならば、大きなシステムと同じだけの努力を払う必要はありません。自分の経験でここを直したら、わけのわからないバグにあったというものはありませんか。そうした解決するために苦労した部分を補ってくれるテストを最低限おこなうだけで良いのです。自分のために作成しましょう。

話をAppleとOrangeクラスに戻しましょう。クラスの依存度・結合度が高いと、単体テストはできず、結合テストからしかテストできないことになります。この場合はAppleとOrageクラスの結合テストになります。

さらに、必要に応じてOrangeクラスをBananaクラスに変更するとなると、Appleクラスも書き換える必要が起き、再度テストが必要になります。OrangeクラスをいじっただけでもAppleクラスとの結合テストをやり直さなくてはなりません。

そこで、こうしたクラス生成の依存をコードの中に記述するのではなく、コードの外から指定してあげる事を依存性の注入と呼ばれます。

クラスの生成を外側からコントロールできればAppleクラスの中には直接Orageクラスの生成を書く必要はありません。

Laravelでは、これを実現するためにIoCクラスが用意されています。

具体的には

前回の例をそのまま使いましょう。Appleクラスは上記の通りです。では、Orangeクラスです。

class Orange {
    function to_string() {
        return 'オレンジ';
    }
}

クラスAppleのgetMixDrink()でOrangeクラスのインスタンス生成をハードコードしているため、単体テストはこのままではできません。そこでIoCクラスを使用してみましょう。

applicationフォルダーの下のroutes.phpもしくはstart.phpに以下のコードを追加します。(実際は使用される前に宣言されていれば、どこでも構いません。)

IoC::register('fruit-orange', function() {
    return new Orange;
}

これで、fruit-orangeという名前(クラス名ではありません。IoCへの登録名です。)で、Orangeクラスの生成コードを登録しました。早速これを使い、Appleクラスを書き換えてみましょう。

class Apple {
    fucntion getDrink() {
        return 'りんごジュース';
    }
    function getMixDrink() {
        $orange = IoC::resolve('fruit-orange');
        return $orange->to_string().$this->getDrink();
    }
}

何だ、たいして変わってないじゃないかと思うでしょう。そのとおりです。IoCクラスが面倒を見てくれますので、Laravelの使用者から見れば、IoCクラスはインスタンスを生成し、管理してくれる便利なクラスという事になります。これをDIコンテナと言います。

依存性の挿入を英語にするとDependency Injectionで、頭文字を取ってDIです。で何故IoCという名前かって?最初このパターンはIoC、Inversion of Control、制御の逆転と呼ばれていました。しかし名前から何を表すのか理解できないので、このパターンを使用している人達が話しあって、DIと改名したそうです。なぜ、挿入なのかといえば、コード自身に含まず、他の場所で定義しているからです。インスタンスの生成という依存関係を内部に持たず、外部から与えてあげるから挿入という言葉が使われます。

しかし、一説には全般的な広い意味合いはIoCで、外部ファイルに依存を定義することをDIと呼ぶと狭い定義をする人もいるようです。まあ、どちらでも私達には関係ありません。コンピューターの世界で用語の統一がとれていないなんてざらですから。便利に使用できれば良いのです。

さて、上記のAppleクラスに戻りましょう。Orangeクラスの生成コードを直接書いていません。そして、重要なのはIoCクラスを使用しているため、IoCへ登録する生成コードを変更すれば、fruit-orangeで生成するクラスを変更できるのです。

問題はどう単体テストを行うかです。fruit-orangeで生成するクラスをAppleクラス専用のテスト用クラスを用意することで、テストすることが可能になりました。テスト時はテスト用のクラスに切り替えれば良いわけです。

例えば、こんなテスト用のクラスを考えましょう。

class AppleTest1 {
    function to_string() {
        return 'XXXXX';
    }
}

そしてテストはAppleクラスのgetMixDrink()メソッドが'XXXXXりんごジュース'を返すことを確認します。もちろんテスト時はfruit-orangeで生成されるクラスをAppleTest1に切り換えます。

同様にこのようなテストも忘れずに。

class AppleTest2 {
    function to_string() {
        return '';
    }
}

AppleクラスのgetMixDrink()メソッドは'りんごジュース'を返すことが期待されます。テストしましょう。単体テストでは、このような値がない場合、ある値を基準にしているならばその値と前後の値、いつもプラスが期待されている引数なら、マイナスを渡した場合どうなるかなどもきちんとパターンに入れておきましょう。上手く動くのが当たり前、動かないのが当たり前のパターンを含めます。まあ、ここらへんの話は、本筋では無いので、このへんで。

いずれにせよ、これで単体テストが可能になりました。もしOrangeがBananaクラスに変わったとしても、IoCの登録を変えるだけです。Appleクラスのコードは変更しなくてよいため、単体テストの必要もありません。(もちろん、Bananaクラスを新しく作成したら、Bananaクラスの単体テストは必要です。)

これがLaravelのIoCクラスを使用してできる基本です。ちなみに、依存性を注入するためにLaravelのIoCコンテナを使用しなければならないわけではありません。使用しなくても可能ですが、IoCクラスを利用すれば簡単に実現できます。