依存性の注入とは

Laravel のドキュメントを見ていると「依存性の注入」という言葉が良く出てきます。依存性の注入は Dependency Injectionと言われ、「DI」と表記されます。また、別の表現では Inversion of Control(制御の反転)とも言われ、「IoC」と表記されます。Laravel のドキュメントでは「 loC」の方が使われています。

Laravel では、依存性の注入という概念を核としてフレームワークが作成されています。この概念を理解することが、Laravel を理解することへの近道になるかと思います。

この依存性の注入とは何でしょうか?
今回は簡単なPHPのサンプルで、この概念を理解したいと思います。
※ Laravelを使ったソースコードでは無いのでご注意ください。


依存性の注入を使っていないケース

<?php
// send_message.php

/*
 * Class
 */

interface SenderInterface {
    public function send($message);
}

class MailSender implements SenderInterface {  // ①
    public function send($message) {
        // ここで、メールでメッセージを送る

        return "メールで送りました。";
    }
}

class BikeSender implements SenderInterface {  // ②
    public function send($message) {
        // ここで、バイクに乗ってメッセージを届ける

        return "バイク便で届けました。";
    }
}

class Messenger {
    protected $sender;

    public function __construct() {
        $this->sender = new MailSender();  // ③
    }

    public function send($message) {
        return $this->sender->send($message);
    }
}


/*
 * Main
 */

// メールで送る
$messenger = new Messenger();  // ④
echo $messenger->send("合格通知") . "\n";  // "メールで送りました。"

SenderInterface を持つ、①MailSenderと②BikeSenderクラスがあります。
③Messengerクラスではコンストラクタで直接 MailSenderクラスをnewしています。
その為、④Main プログラムで、送信手段をメールからバイク便に変更しようとすると、③Messengerクラスのコンストラクタを修正する必要があります。

この状態を「Messengerクラスが、MailSenderクラスに依存している」と言います。


依存性の注入を使ったケース

<?php
// send_message_di.php

/*
 * Class
 */

interface SenderInterface {
    public function send($message);
}

class MailSender implements SenderInterface {  // ①
    public function send($message) {
        // ここで、メールでメッセージを送る

        return "メールで送りました。";
    }
}

class BikeSender implements SenderInterface {  // ②
    public function send($message) {
        // ここで、バイクに乗ってメッセージを届ける

        return "バイク便で届けました。";
    }
}

class Messenger {
    protected $sender;

    public function __construct(SenderInterface $sender) {  // ③
        $this->sender = $sender;
    }

    public function send($message) {
        return $this->sender->send($message);
    }
}


/*
 * Main
 */

// メールで送る
$messenger = new Messenger(new MailSender());  // ④
echo $messenger->send("合格通知") . "\n";  // "メールで送りました。"

// バイク便で送る
$messenger = new Messenger(new BikeSender());  // ⑤
echo $messenger->send("合格通知") . "\n";  // "バイク便で届けました。"

③Messengerクラスのコンストラクタで、SenderInterfaceのクラスを取得するよう変更しました。これで特定のクラスへの依存を排除できました。
④⑤Main プログラムで送信手段を変更する事が出来るようになりました。

Messengerクラスの依存性を外部から注入できるようになり、柔軟性と保守性が上がりました。


DI コンテナを使ったケース

さらに一歩進めて、DI コンテナという仕組みを導入してみます。
この例では DI コンテナに Pimple を使用します。

https://github.com/silexphp/Pimple

まず、composerでカレントディレクトリにPimpleをインストールします。

composer require pimple/pimple:~3.0

DI コンテナを使ったサンプル

<?php
// send_message_di_container.php

require "vendor/autoload.php";  // ① Pimpleをオートロード

/*
 * Class
 */

interface SenderInterface {
    public function send($message);
}

class MailSender implements SenderInterface {
    public function send($message) {
        // ここで、メールでメッセージを送る

        return "メールで送りました。";
    }
}

class GMailSender implements SenderInterface {  // ②
    public function send($message) {
        // ここで、GMailでメッセージを送る

        return "GMailで送りました。";
    }
}

class BikeSender implements SenderInterface {
    public function send($message) {
        // ここで、バイクに乗ってメッセージを届ける

        return "バイク便で届けました。";
    }
}

class Messenger {
    protected $sender;

    public function __construct(SenderInterface $sender) {
        $this->sender = $sender;
    }

    public function send($message) {
        return $this->sender->send($message);
    }
}

/*
 * Config
 */

$container = new Pimple\Container();        // ③ DIコンテナ

$container['mail'] = function($container) {  // ④
    // return new MailSender();
    return new GMailSender();
};

$container['bike'] = function($container) {  // ⑤
    return new BikeSender();
};


/*
 * Main
 */

// メールで送る
$messenger = new Messenger($container['mail']);  // ⑥
echo $messenger->send("合格通知") . "\n";

// バイク便で送る
$messenger = new Messenger($container['bike']);  // ⑦
echo $messenger->send("合格通知") . "\n";

①composerでインストールしたPimpleをオートロードします。
②GMailSenderを追加しました。
③DIコンテナとしてPimpleをnewします
④⑤ ‘mail’, ‘bike’ というキーワードに対してどのクラスを生成するかの設定をします。
⑥⑦DIコンテナにキーワードを渡すことでSenderInterfaceを持つクラスを生成します。

Configとして、DIコンテナにクラスの生成を委譲することで、Mainプログラム部分も依存性を排除することができました。メールで送信する時の実際の手段を②GMailSenderに変更する時は、④DIコンテナを修正します。

DIコンテナを導入したことで、MessengerクラスとSenderInterfaceを持つクラスの対応付けを1箇所に集約することができ、柔軟性、保守性ともに、アップしました。

上記のサンプルでは Mainプログラムの部分のコードが少ないので、DIコンテナの恩恵があまり感じられないかもしれませんが、コートがもっと増えて、クラスをnewする箇所があっちこっちに出てくるようであれば、このDIコンテナの導入が非常に有効になってきます。


まとめ

依存性の注入のイメージはなんとなく掴めたでしょうか?
今回のサンプルではDI コンテナとして Pimpleを使いましたが、Laravel では Illiminate\Foundation\Application クラスが DI コンテナにあたります。DI コンテナを中心としてサービスプロバイダやファサードが作られています。

また、今回のサンプルにはありませんでしたが、DIを導入すると、テストコードを書く際に、クラスをスタブやモックに入替えることが出来るようになります。もしかするとこちらの方がメリットが大きいのかもしれません。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中