DI のよくある質問(FAQ)
- DI は IoC の別名ですか?
- Service Locator とは何ですか?
- DI を使わないほうがよいのはどんなときですか?
- DI を使うことに欠点はありますか?
- 古いアプリケーションを DI に書き換えるには?
- なぜ継承よりコンポジションが好まれるのですか?
- Nette DI Container は Nette の外でも使えますか?
- なぜ設定は NEON ファイルなのですか?
- NEON ファイルの解析はアプリケーションを遅くしませんか?
- 設定ファイルのパラメータにクラスからアクセスするには?
- Nette は PSR-11 Container インターフェースに対応していますか?
- container、compiler、definition などの用語は何を意味しますか?
DI は IoC の別名ですか?
制御の反転(Inversion of
Control、IoC)は、プログラムの中で制御がどう流れるかを述べる原則です。あなたのコードが外部のコードを呼ぶのか、それとも(フレームワークのような)外部のコードがあなたのコードを呼ぶのか、という話です。IoC
は幅広い概念で、イベント、いわゆるハリウッドの原則などを含みます。原則 3:
ファクトリに任せるで扱うファクトリもこの概念に含まれ、new
演算子の反転にあたります。
依存性注入(Dependency Injection、DI)は、オブジェクトがどうやって依存関係(つまり一緒に働く必要のあるほかのオブジェクト)を手に入れるかに焦点を当てます。オブジェクト自身に作らせたり探させたりするのではなく、依存関係を明示的に渡すことを勧めるデザインパターンです。
ですから DI は IoC の特定の形と見なせます。ただし IoC のすべての形がきれいなコードを促すわけではありません。たとえばグローバル状態や Service Locatorパターンに頼る手法は、アンチパターンです。
Service Locator とは何ですか?
依存性注入に代わる方法です。中心となるオブジェクト(ロケーター)があり、利用できるすべてのサービス(依存関係)がそこに登録されます。オブジェクトが依存関係を必要とすると、Service Locator に要求します。
しかし DI と比べると透明性に欠けます。依存関係が API(コンストラクタやメソッド)に現れず、オブジェクトのコードの中(ロケーターの呼び出し)に隠れてしまうので、つながりを知るにはコードを読まなければなりません。テストも複雑になります。オブジェクトを作るときに単にモックの依存関係を渡すことができず、Service Locator 自体を操作する必要がしばしば出てくるからです。さらに Service Locator は不要な依存関係を持ち込みます。DI ではオブジェクトが理想的にはコンテナの存在を知らずに済むのに対し、こちらではオブジェクトがロケーターと結びついてしまいます。
DI を使わないほうがよいのはどんなときですか?
依存性注入のデザインパターンを正しく使うことに、知られた大きな欠点はありません。むしろ、グローバルにアクセスできる場所(静的プロパティやシングルトンなど)から依存関係を得ることは数々の面倒につながりますし、Service Locator を使うのも同様です。ですから DI を使うことは一般に常におすすめできます。これは教条ではありません。単に、依存関係をきれいに管理するこれより良い代案が広く受け入れられていない、というだけのことです。
とはいえ、オブジェクトにグローバルにアクセスしてもよい、限られた特定の場面はあります。たとえばデバッグ中に、変数の値をダンプしたり、実行時間を測ったり、ある地点でメッセージを記録したりする場合です。あとでコードから取り除く一時的な処置であれば、グローバルにアクセスできるダンパー、タイマー、ロガーを使うのは正当です。これらの道具はアプリケーションの設計の中核ではありません。
DI を使うことに欠点はありますか?
依存性注入を使うと、コードを書く手間が増えたり性能が落ちたりといった不利益はあるのでしょうか。DI に沿ってコードを書き始めると、私たちは何を失うのでしょうか。
DI 自体が実行時の性能やメモリ使用量に与える影響はごくわずかです。DI コンテナの性能は要因になり得ますが、Nette DIはコンテナを素の PHP コードにコンパイルするので、アプリケーションの実行中のオーバーヘッドは事実上ゼロです。
DI の原則に従ってコードを書くと、依存関係を受け取るコンストラクタを作る必要が出てきます。かつては面倒に思えたかもしれませんが、現代の IDE や PHP 8 のコンストラクタのプロパティ昇格といった機能のおかげで、とても速く書けます。ファクトリは Nette DI がしばしば自動生成してくれるので、定型コードはさらに減ります。その一方で、シングルトンや静的なアクセサを書く必要はなくなります。
全体として、DI を使ったよく設計されたアプリケーションは、シングルトンやグローバルアクセスに頼るものと比べて、目立って短くも長くもなりません。依存関係の生成と結びつけに関わるコードが、個々のクラスから専用の場所、つまり DI コンテナの設定とファクトリに移るだけです。
古いアプリケーションを DI に書き換えるには?
古いアプリケーションから依存性注入への移行は、とくに大きく複雑なアプリケーションでは骨の折れる作業になり得ます。この過程には体系的に取り組むことが大切です。
- 依存性注入に移るときは、使う原則と作法をチームの全員が理解していることが大切です。
- まず既存のアプリケーションを分析し、主要な部品とその依存関係を洗い出します。どの部分をどの順序で書き換えるかの計画を立てます。
- DI コンテナを実装するか、できれば Nette DI のような既存のライブラリを使いましょう。
- アプリケーションの各部分を、依存性注入を使うよう少しずつ書き換えます。コンストラクタやメソッドを、依存関係をパラメータとして受け取る形に変えることになるでしょう。
- オブジェクトを生成しているコードを、コンテナから取得するか、コンテナが提供するファクトリを使うよう更新します。
依存性注入への移行は、コードの品質とアプリケーションの長期的な保守性への投資であることを覚えておいてください。これらの変更は大変かもしれませんが、その結果はよりきれいで、モジュール化され、テストしやすく、将来の拡張と保守に備えたコードになるはずです。
なぜ継承よりコンポジションが好まれるのですか?
コードの再利用には、継承よりコンポジションが一般に好まれます。結びつきがより緩やかになるからです。コンポジションなら、基底クラスの変更が依存するサブクラスを壊す問題に出会いにくくなります。典型的な例が、コンストラクタ地獄と呼ばれる状況です。
Nette DI Container は Nette の外でも使えますか?
もちろんです。Nette DI Container は Nette の一部ですが、フレームワークのほかの部分と切り離して使える独立したライブラリとして設計されています。Composer でインストールし、サービスを定義する設定ファイルを作り、数行の PHP コードで DI コンテナを作るだけです。すぐにあなたのプロジェクトで依存性注入の恩恵を受け始められます。
Nette DI Containerの章で、コードの例とともに具体的な使い方を説明しています。
なぜ設定は NEON ファイルなのですか?
NEON は、アプリケーション、サービス、その依存関係を設定するために Nette の中で生まれた、単純で読みやすい設定言語です。JSON や YAML と比べて、この目的にははるかに直感的で柔軟な選択肢を提供します。NEON なら、JSON や YAML では同じくらい明快に表すのが難しい、あるいは不可能なサービスの定義や関係を、自然に書けます。
NEON ファイルの解析はアプリケーションを遅くしませんか?
NEON ファイルの解析はとても速いのですが、その速さは本番ではほとんど関係ありません。設定ファイルが解析されるのは、アプリケーションが最初に動くとき(または設定が変わったとき)の一度きりだからです。解析後は DI コンテナのコードが生成されてキャッシュされ(ディスクに保存され)、以降のリクエストではこのコンパイル済みの PHP コードが実行されるので、それ以上の解析は不要です。
これが本番環境での動きです。開発中は、内容が変わるたびに NEON ファイルが解析されるので、開発者は常に最新の DI コンテナを手にできます。すでに述べたとおり、解析そのものはとても高速です。
設定ファイルのパラメータにクラスからアクセスするには?
原則 1: 渡してもらうを思い出してください。クラスが設定ファイルの情報を必要とするなら、そのクラスがどうやってそれを取ってくるかを考えてはいけません。代わりに、たとえばクラスのコンストラクタでそれを要求するだけです。そして設定ファイルでその値を与えます。
次の例では、%myParameter% が myParameter
パラメータの値のプレースホルダーで、MyClass のコンストラクタに渡されます。
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
複数のパラメータを渡したい場合やオートワイヤリングを使いたい場合は、パラメータをオブジェクトにまとめると便利です。
Nette は PSR-11 Container インターフェースに対応していますか?
Nette DI Containerは PSR-11 に直接は対応していません。ただし Nette DI Container と、PSR-11 の Container インターフェースを期待するライブラリやフレームワークとの相互運用が必要なら、Nette DI Container と PSR-11 の橋渡しをする簡単なアダプタを作れます。
container、compiler、definition などの用語は何を意味しますか?
Nette DI のまわりで繰り返し登場する言葉、そのほとんどは拡張を書くときに出てくるものですが、その短い用語集です。
- Container –
サービスを必要に応じて作り、実行時に保持するコンパイル済みのオブジェクト(
Nette\DI\Container)。最適化された PHP コードとして一度だけ生成されます。 - Compiler – 設定ファイルと拡張を、そのコンテナのクラスに変える仕組み。
- ContainerBuilder – コンパイル中に使われる、変更可能なコンテナのモデル。実際のサービスがまだ存在しない段階でサービスの定義を保持します。拡張の作成をご覧ください。
- Service – コンテナが管理するオブジェクト。ふつう一度だけ作られて共有されます(シングルトン)。データベース接続、メーラー、ロガーなどです。
- Definition – サービスのレシピ。その型、作り方、作ったあとに何をするか。Nette は定義をコンテナのファクトリメソッドに変えます。いくつかの種類があります(定義の種類をご覧ください)。
- Type – サービスのクラスやインターフェース。オートワイヤリングが、それを必要とする場所にサービスを結びつけるために使います。
- Autowiring – 型にもとづいてサービスをコンストラクタやメソッドへ自動的に渡すこと。依存関係を手で結びつけずに済みます。
- Tag – 定義に付けるラベル(値を伴うこともあります)。拡張は
findByTag()で、それを持つすべてのサービスを見つけられます。 - Setup –
サービスが作られた直後に行われる追加の呼び出し。メソッドの呼び出しやプロパティへの代入で、
addSetup()で足します。 - Alias – 既存のサービスの別名。