「速く、簡単に、もっときれいに:DIコンテナとサービスロケータを区別する方法について」を翻訳しました
PHPメンターズ道場生 kumamidori です。 PHPのエキスパートとして世界的に知られている方の1人に、Paul M. Jonesさんがいらっしゃいます。 フレームワーク「Aura for PHP」のリードであり、PHP-FIGの策定メンバーに入られている方です。 通称 pmjones さんのブログで、昨年末、DIに関する下記エントリがありました。 「Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator」 興味深い内容のようだったので、翻訳しました。翻訳記事の公開について、著者ご本人から快諾頂けたため、掲載させて頂きます。 本文中にあるとおり、この記事に対する訂正として、「Quicker, Easier, More Seductive: Names, Usage, and Intent」という記事がありますので、併せてご確認下さい。 記事の内容について、皆様はどのように感じられるでしょうか。人によって、同意や疑問等、いろいろあるのではないかと想像します。
速く、簡単に、もっときれいに: DIコンテナとサービスロケータを区別する方法について
2013年12月16日 pmjones
(このふいに始まったシリーズでは、 サービスロケータ対 Dependency Injection について語ってきました)。 更新情報: このシリーズの次の記事で、重要な訂正をしています。 また、この記事の後半については、後から追記を加えました。併せてご確認下さい。 DIコンテナとサービスコンテナは混同しがちです。両者はよく似ています。たしかにほとんど違いがありません。 DIコンテナをサービスロケータとして使うこともできます。 しかし、DIコンテナとしてサービスロケータを使うことは難しいと私は考えています。 両者とも、制御の反転(Inversion of Control)と呼ばれている、一般的なパターンのサブパターンです。 用語として、 Dependency Injectionを、より一般的な用語である制御の反転(Inversion of Control)と混同する人がいます。
違いをどう説明したら良いか
どういったルール、目安によって、どっちがどっちなのかを易しく判別できるか、検討しました。 今のところ、「新しいインスタンスを、サービスを定義することなく生成できるか?」だと考えています。 生成できるのであれば、それはDIコンテナ。 そうでなければ、それはサービスロケータです。 このフレーズには、いくつかバリエーションがあります:
クラスの新しいインスタンスを、サービス定義を持つことなしに、生成できるのならば、それはDIコンテナです。
コンテナの外でオブジェクトを取得するのに、サービスを定義する必要があるのならば、それはサービスロケータです。
クラスの新しいインスタンスを、サービスとしてそれを定義することなく生成することができないのであれば、それはサービスロケータです。
これによって私は、コンテナで、サービスとしてインスタンス定義を持つことなく、新しい、個々の、独立したクラスのインスタンスを繰り返し生成することができるべきだと言おうとしているわけです。
例
上記のルールを踏まえた上で、例として、Aura.DIを使った Dependency Injection のサンプルを示します。
// これらは別々のインスタンスであり、サービスを共有していません $foo_1 = $di->newInstance('Foo\Bar\Baz'); $foo_2 = $di->newInstance('Foo\Bar\Baz'); var_dump($foo_1 === $foo_2); // false
コンストラクタパラメータの設定や、セッタなどが要るかもしれません。 newInstance() を呼び出しする時のために?そうです。 Aura.DI は、コンストラクタパラメータやセッタメソッドの継承や上書きができるようになっています。
// __construct($gir) として与えられます $di->params['Foo\Bar\Baz']['gir'] = $di->newInstance('Dib\Zim\Gir'); // セッタメソッドとして setOperation($operation_name) が与えられます $di->setter['Foo\Bar\Baz']['setOperation'] = 'ImpendingDoom';
要点は、DIコンテナはいつでもクラスを作ることができて、 再利用されるサービスとしてクラスの定義を持つことはなくできるということです。 一方、サービスロケータは、サービスとしての定義を必要とします。 そして、コンテナからサービスの共有インスタンスを取得することができます。 このシリーズの初期の方からよくある私のサービスロケータの例を示します。
// 共有されるサービスを定義する $locator->set('gir', function () use ($locator) { return new \Dib\Zim\Gir; } $locator->set('foo', function () use ($locator) { $foo = new \Foo\Bar\Baz($locator->get('gir')); $foo->setOperation('ImpendingDoom'); return $foo; }); // これらは共有されているサービスで、インスタンスは分離していません $foo_1 = $locator->get('foo'); $foo_2 = $locator->get('foo'); var_dump($foo_1 === $foo_2); // true
誰が何をやっているか?
このルールを満たしているかどうか確かめようと、PHP界におけるコンテナをいくつかちょっと見てみました。 私が調べたのは下記です。
Aura.Di: DIコンテナ
Laravel Container: サービスロケータ DIコンテナ
Pimple (Silex などで使われている): サービスロケータ
Slim (via $app): サービスロケータ
Symfony DependencyInjection component: サービスロケータ (?!)
Zend Di: DIコンテナ
分かったこと:
Laravel:
ドキュメントでは、コンテナをIoC(制御の反転)コンテナと呼んでいます。 しかし、一貫して Dependency Injection について言及されています。決してサービスロケータとは言っていません。 コンテナの使い方を見ると、サービスを必要とするクラスに、コンテナそれ自身がインジェクトされるために、 あとのコードベースはすべて事実上サービスロケータであることは明らかです。 クラス自体は一般的な方にネーミングされているから、ドキュメントの方を修正すれば良いことに気づきました。
追記:
プロジェクトリードから、コンテナの public make() メソッドは、新しいインスタンスを生成できると私に連絡がありました。 細かいことを言わせて頂くなら、ネーミングが余計なのだけれども (私見ですが、$abstract や make() ではなくて、 $class や newInstance() を使う方がわかりやすいでしょう)、 前述のルールについては満たしています。
Pimple:
おもしろいことに、Pimpleは、独立したサービスのインスタンスを返すfactoryメソッドを持っています。 先ほどのルールをクリアしていると思う人がいるかもしれませんが、私はそうは思いません。 結局、まだサービスの定義が必要になっているからです。 特定のクラス名のために、ファクトリとしてサービスを定義するかもしれないし、 ファクトリされたクラスのためにサービスを命名するかもしれませんけれど、 この、ファクトリがほしいクラスのためにサービスを定義する必要があるということから、本当に同じとは言えないと思います。
Slim:
$app をサービスロケータとして使っています。 分かっていて、Josh Lockhart はサービスロケータともDIコンテナとも言っていません。 @codeguy はこの話題を両方避けていてさすがです。
Symfony DependencyInjection component:
コードベースを私が少し見たところ、クラスのインスタンスを生成する方法が明らかではありませんでした。 機能が無いのではなくて、私がまだ見つけられていないだけの可能性が高いです。 Symfony使いさんがこれを読んでいたら、私に教えて下さい。Symfony DI component でどうやってクラスのインスタンスを生成するのか。 そうしたら、この記事をアップデートします。
結論
ルールに照らし合わせてみると、Dependency Injection システムだと言っているものの一部は、サービスロケータとして説明する方が適切でした。 一般的には、コンテナ(制御の反転 Inversion of Control コンテナ)かもしれないけれど、それはDIコンテナではありません。 DIコンテナでないことが間違っているのではなくて、 これらを間違った名前で使っていることで、話が混乱して、複雑になってしまうのです。
1. ポールがどんなルールを作ろうが、ある種の人たちが好きなシステムは本当にDIコンテナなのだ
2. そんな区別は実際大した問題ではない。どうなっていようが、好きなシステムを使えば良いだけだ。
コメントお待ちしております:-)
後書き
レガシーなPHPコードベースに困惑していませんか? もしそれを改善したいのであれば、どこから始めれば良いのか分かるでしょうか。 私が近々出す本、「Modernizing Legacy Applications in PHP(PHPでレガシーアプリケーションをモダンにする)」 は、小さな、インクリメンタルな変更を重ねるステップを踏むことによって、レガシーなコードベースの質を大幅に改善できるというガイドになっています。 詳しくは、出版前の文章を購読できる下記のメーリングリストで! http://mlaphp.com/











