DI 依存性の注入とはそもそもなんのためか
まずは調べてみると下記のような回答が見受けられる。
"依存性の注入(DI: Dependency Injection)は、ソフトウェア設計の原則であり、コードの柔軟性、再利用性、およびテスト容易性を向上させるために広く使用されています。"
この太文字の部分が使う主な理由とのこと。
イメージしやすいように具体例をあげてみる
例:車とエンジン
想像してみてください。あなたは「Car」というクラスを設計しているとします。この車はエンジン(Engineクラス)を必要とします。
この設計では、Carクラスは直接Engineクラスに依存しています。Engineクラスの実装を変更すると、Carクラスも影響を受ける可能性があります。また、Carクラスをテストする際にEngineクラスを切り離すのが難しくなります。
★依存性の注入なしの場合
dart
class Engine {
void start() {
// エンジンをスタートするロジック
}
}
class Car {
Engine engine;
Car() {
engine = Engine(); // Carクラスが直接Engineを作成
}
void startEngine() {
engine.start();
}
}
★依存性の注入ありの場合
class Engine {
void start() {
// エンジンをスタートするロジック
}
}
class Car {
Engine engine;
Car(this.engine); // エンジンが外部から注入される
void startEngine() {
engine.start();
}
}
※例のようにコンストラクタ引数を通じてEngineオブジェクトをCarオブジェクトに渡すことは、依存性の注入(DI: Dependency Injection)の一例となる。
この例で言うとCar classは外部のEngineを引数として扱うため、テストや機能追加をする際に
- Carは部品(オブジェクト)のようにEngineを使用しているためエンジンが故障(エラーなど)しても中身を修理したり、新しい部品に変えること(新しいエンジンクラスを作る)で問題なく動く。
- 依存性の注入なしの場合は車から取り外しにくいようにガチガチに固定されているイメージ?なので後で不便ですよ〜という認識だからこそ、シンプルなアプリの場合は気にする必要ないが実務や規模が大きくなると必須になってくる。
一見DIの有無でコードを見比べた際に
依存性の注入なし
Car() {
engine = Engine(); // Carクラスが直接Engineを作成
}
依存性の注入あり
Car(this.engine);
//この二つの差はCar class内でインスタンスを作成するかコンストラクタ引数でengineを引っ張ってくるかの違いが
依存性の注入あり・なしの差。
コンストラクタ引数 = 依存性の注入。
植物を直接地面に植えるか植木鉢に入れるかの差も例えとして使えるかも、、。
植物が枯れたときに土が原因か、環境か、根っこなのか。
植木鉢だと一つ一つ分かれているので原因特定がしやすいが、
地面に植えられていると見るべきポイントが多くて結構大変というイメージ。
この例え話は自分で考えたので多少の違いはあるかもしれないがとてもわかりやすい例だと思った。
今回はコンストラクタ引数を通じた依存性の注入についてが話の中心になったが、
概念だったりなんのため?どういうこと?という部分が少しでも晴れたのではないでしょうか?
ちなみによく使われるものとして
・メソッド注入
・プロパティ注入
も依存性の注入の方法としてある。
それぞれのユースケースとして
メソッド注入
・後から依存性を設定する必要がある場合:
オブジェクトが初期化された後に、依存関係を設定したり変更したりする必要がある場合に有効です。例えば、設定が動的に変わる場合や、特定の条件下でのみ依存関係が必要な場合などです。
・オプショナルな依存関係:
あるクラスが特定の依存関係を必ずしも必要としないが、提供された場合に追加の機能を提供する場合に適しています。
・循環依存の解消:
二つのクラスが相互に依存している場合、コンストラクタ注入では循環依存が生じることがあります。このような場合、メソッド注入を利用して依存関係を解消することができます。
プロパティ注入
・設定やオプションの柔軟な変更:
アプリケーションの実行時に依存関係の設定を変更したい場合に適しています。たとえば、DIフレームワークやコンテナがプロパティを自動で注入する環境では、この方法がよく使用されます。
・フレームワークやライブラリの要件:
特定のフレームワークやライブラリでは、プロパティ注入が推奨される場合があります。たとえば、特定のフレームワークがコンストラクタ注入をサポートしていない場合などです。
・テストの簡略化:
ユニットテストの際に、テスト対象のクラスに簡単にモックオブジェクトを注入するために使われることがあります。