DIとサービスロケータの違い
DIとサービスロケータは、いずれもオブジェクトの構築と依存の解決という仕事を切り出すためのパターンです。ところで、この2つのパターンの違いを明確に説明できるでしょうか?
Pimpleでシンプルに正しくDIを理解する のコードは以下のようになっていました。
<?php
require_once '../vendor/pimple/pimple/lib/Pimple.php';
// インフラ
interface MailerInterface
{
public function send($body);
}
class SendmailMailer implements MailerInterface
{
public function send($body)
{
}
}
// ドメイン
class NewsletterTransfer
{
protected $mailer;
public function __construct(\MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function send($newsletter)
{
$this->mailer->send($newsletter);
}
}
$container = new \Pimple();
// オブジェクトコンストラクションのコンフィギュレーション
$container['infrastructure.mailer'] = function($container) {
$mailer = new \SendmailMailer();
return $mailer;
};
$container['domain.transfer.newsletter'] = function($container) {
$newsletterTransfer = new \NewsletterTransfer($container['infrastructure.mailer']);
return $newsletterTransfer;
};
// オブジェクトの利用
$newsletterTransfer = $container['domain.transfer.newsletter'];
$newsletterTransfer->send('ニュースレター本文');
DIの仕組みを解説するためのこのコードには、実は、DIパターンとサービスロケータパターンとが混在しています。このコードから、オブジェクトごとのまとまりを意識してパーツを取り出すと、次の3つになります(利用側から順に並べています)。
<?php
// オブジェクトの利用
$newsletterTransfer = $container['domain.transfer.newsletter'];
$newsletterTransfer->send('ニュースレター本文');
<?php
// ドメイン
class NewsletterTransfer
{
protected $mailer;
public function __construct(\MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function send($newsletter)
{
$this->mailer->send($newsletter);
}
}
<?php
// インフラ
class SendmailMailer implements MailerInterface
{
public function send($body)
{
}
}
図にすると、次のようになります。
どこがDIで、どこかサービスロケータなのでしょうか? また、この3つのオブジェクトの仲介役として機能するコンテナは、DIコンテナなのでしょうか?サービスロケータ(の対象コンテナ)なのでしょうか?
オブジェクトから見たコンテナの位置づけ
DIなのかサービスロケータなのか、この2つの違いを明確にするには、オブジェクトがコンテナに何を求めているのかを理解しなくてはなりません。
必要なオブジェクトをコンテナから取り出す(能動的)
上のコードの入口部分にあたる「オブジェクトA」をAさんが開発しているとしましょう。Aさんの視点だけをピックアップしたのが次の図です。
オブジェクトBやオブジェクトCの開発はAさんの担当ではありません。オブジェクトBがどのように構築されるのかは、コンテナに定義されているため、Aさんは単にコンテナからオブジェクトAを取り出して、使うだけです。
オブジェクトAからは、コンテナを直接参照し、名前をキーにして使いたいオブジェクトを取り出します。
オブジェクトBがどのように作られるのかを気にすることなく、完成し たオブジェクトBをただ使うという目的を達成するために、コンテナから名前をキーにしてオブジェクトを取り出す、これがサービスロケータのパターンです。
オブジェクトAは、コンテナの外側でインスタンス化されます。コンテナの管理下にないこのようなオブジェクトでは、直接コンテナオブジェクトを道具として参照して使う必要があるわけです。
必要なオブジェクトの受け口だけ用意して受け取る(受動的)
別の担当者Bさんは、オブジェクトBを開発しています。Bさんの視点だけをピックアップしたのが次の図です。
オブジェクトBの実装には、オブジェクトC(のインターフェイス)を使っています。このオブジェクトCは、さらにD、E・・・を使っているかもしれませんが、オブジェクトCから先はBさんの担当ではありません。ただ単にオブジェクトCを使い、自分の担当であるオブジェクトBの処理を実装したいだけです。
このプロジェクトでは、オブジェクトBは、必ずコンテナ経由で利用する、という取り決めがあると思ってください。つまり、オブジェクトBはコンテナの管理下にあるわけです。このような場合、オブジェクトBの生成時に、コンストラクタの引数や追加のセッター呼び出しによって、必要な依存オブジェクトをコンテナが自動的にセットするようにできます。オブジェクトBは、「受け取る道具の種類」をコンストラクタの引数で示してはいますが、自ら取り出しているわけではありません。
オブジェクトCがどのように作られるのかを気にすることなく、完成したオブジェクトCをただ使うという目的を達成するために、使いたいオブジェクトの受け取り口だけ用意して、コンテナがそこへ渡してくれる、というのがDIのパターンです。
まとめ
同じコンテナでも、それを使うオブジェクトがコンテナの管理下にあるかどうかで、使える手法が異なります。プロジェクトで利用するフレームワークとの兼ね合いで、コンテナ管理下におけない状況などがあります。
重要なのは、DIでもサービスロケータでも、依存の解決とオブジェクトの構築という仕事をオブジェクト自身からコンテナへ移しているのは同じだということです。これによって、Aさん、Bさんはそれぞれの担当範囲のみに集中できます。オブジェクトのコードから余計なものが取り除かれ、自分の仕事により局所化されるようになります。