一緒に Kotlin を学びましょう: コンセプト: 25. Dependency Injection
Kotlin での依存関係の注入と Hilt の使用、シリーズ 1: 依存関係の注入の概要
この連載ブログでは主に以下の内容を紹介していきます。
Dependency Injection
(Dependency Injection) 概念の紹介。インターネット上で DI に関する多くの紹介を読みましたが、霧の中にいます。ここでは分かりやすくご紹介します。- 手動依存性注入の概要。Hilt を誰にでも理解しやすくするために、まず依存関係注入の効果を手動で実現する方法を紹介します。
- Hilt アノテーション (注釈) の導入と使用例
- MVVM ケースで Hilt を使用する方法
このブログでは主にDependency Injection
(Dependency Injection)の概念を紹介します。
記事ディレクトリ
1 DIとは
DI を理解する前に、まずプログラミングにおける依存関係の意味を理解しましょう。
クラス A がクラス B の機能を使用する場合、それはクラス A がクラス B の依存関係を持つことを意味します。Java では、別のクラスのメソッドを使用する前に、まずそのクラスのオブジェクトを作成する必要があります (つまり、クラス A はクラス B のインスタンスを作成する必要があります)。
簡単に言うと、依存関係の注入は、別のオブジェクト B を必要とする (依存する) オブジェクト A のインスタンスです。オブジェクト B のインスタンスはオブジェクト A に注入する必要があり、この「注入」アクションはプログラマによって手動で実行されるか、実行のためにソフトウェア フレームワークに渡されます。
したがって、オブジェクトを作成するタスクを他の人にオフロードし、依存関係を直接使用することを依存関係注入 (DI) と呼びます。
別の例を次に示します。
ホイール、エンジンなどのさまざまなオブジェクトを含む car クラスがあるとします。依存関係の注入が使用されない場合、車 (クラス) 内に新しいエンジン (インスタンス) を作成することしかできません: private val Engine = Engine():
class Car {
<!-- -->
private val engine = Engine()
fun start() {
<!-- -->
engine.start()
}
}
fun main(args: Array) {
<!-- -->
val car = Car()
car.start()
}
この方法で書くと問題があります。Car クラスは Engine エンジンと密接に結合しています。Car クラスの各インスタンスは、異なる Engine インスタンスを使用します。Car クラス内で「エンジンを構築」するこの方法により、Car クラスのテストと保守が困難になります。車を内燃機関車と電気自動車に区別する必要がある場合は、Car クラスの内部変更を行うだけです。これは、オブジェクト指向ソフトウェア開発の「オープンとクローズの原則」に違反します。
では、DI スタイルのコードはどのようにあるべきでしょうか? Car クラスは、コンストラクターで Engine 型のパラメーターを受け取ります。class Car(private val engine: Engine){ ... }
class Car(private val engine: Engine) {
<!-- -->
fun start() {
<!-- -->
engine.start()
}
}
fun main(args: Array) {
<!-- -->
val engine = Engine()
val car = Car(engine)
car.start()
}
このように、Car クラスは内部で「エンジンを構築する」必要はなく、必要なエンジンをインスタンス化する際に外部から直接渡す(注入する)ことができます。
ここで、Car クラスのインスタンス化は Engine クラスのインスタンスに依存していると言えます。Car クラスのインスタンス car を構築するときに、Engine クラスのインスタンスを渡します (注入します) (インスタンス オブジェクト変数は通常、小文字が最初になります)文字)。
そうすることの利点も明らかです。
- Car クラスは再利用可能で、燃料車が必要で、インスタンス化するときに内燃エンジンを渡し、電気自動車が必要な場合は電気モーター エンジンを渡します。
- Car クラスのテスト可能性が現実になりました (Test Double をテストするためにさまざまなタイプの FakeEngine を渡すことができます)。
2種類のDI
DI には基本的に 3 つのタイプがあります。
- コンストラクター インジェクション (クラス コンストラクター インジェクション): 依存関係はクラス コンストラクターを通じて提供されます。
- セッター注入 (クラス フィールド インジェクション): クライアントは、インジェクターが依存関係を注入するために使用するセッター メソッドを公開します。
- インターフェイス注入: 依存関係は、それを渡すクライアントに依存関係を注入できるインジェクター メソッドを提供します。クライアントは、依存関係を受け入れるセッター メソッドを公開するインターフェイスを実装する必要があります。
上記の例はコンストラクタインジェクション(クラスコンストラクタインジェクション)です。2 番目のタイプ: セッター インジェクション (クラス フィールド インジェクション)。次のコード スニペットに示されていますlateinit var engine: Engine
。
class Car {
<!-- -->
lateinit var engine: Engine
fun start() {
<!-- -->
engine.start()
}
}
fun main(args: Array) {
<!-- -->
val car = Car()
car.engine = Engine()
car.start()
}
したがって、DI の責任は次のとおりです。
- オブジェクトを作成する
- どのクラスがそれらのオブジェクトを必要とするかを知る
- そしてこれらすべての物を彼らに渡します
オブジェクトに変更がある場合、DI はそれを調べますが、それらのオブジェクトを使用するクラスには関係しません。そうすれば、将来オブジェクトが変更された場合、適切なオブジェクトをクラスに提供するのは DI の責任になります。
ちなみに、以前にSOLID原則(オブジェクト指向プログラミングと設計の5つの基本原則)についてブログを書きました。その 5 番目の項目は、クラスは具体性 (簡単に言えば、ハードコーディング) ではなく抽象性に依存する必要があります。これらの原則によれば、クラスは、その責任を実行するために必要なオブジェクトを作成するのではなく、その責任を果たすことに重点を置く必要があります。ここで DI が活躍します。DI はクラスに必要なオブジェクトを提供します。
3 DIのメリットとデメリット
DI の利点:
- 単体テストに役立ちます。
- 依存関係の初期化がインジェクター コンポーネントによって行われるため、ボイラープレート コードが削減されます。
- アプリケーションの拡張がさらに簡単になりました。
- アプリケーション プログラミングで重要な疎結合の実現に役立ちます。
DI のデメリット:
- 学習するのは少し複雑で、使いすぎると管理上の問題やその他の問題が発生する可能性があります。
- 多くのコンパイル時エラーは実行時にプッシュされます。
- DI フレームワークは、リフレクションまたは動的プログラミングを通じて実装されます。これにより、「参照の検索」、「呼び出し階層の表示」などの IDE 自動化や安全なリファクタリングの使用が妨げられる可能性があります。
DI を実装するためのライブラリとフレームワーク:
- 春(ジャワ)
- Google ガイド (Java)
- ダガー (Java および Android)
- キャッスル ウィンザー (.NET)
- ユニティ(.NET)
上記の分析を通じて、依存性注入 (DI) について比較的直感的な印象が得られました。依存関係注入 (DI) の目的は、コード間の分離です。分離の目的は、コードの再利用性とテスト容易性を向上させることであり、コードの再利用性とテスト容易性を向上させることは、コードの堅牢性を強化することです。