一緒に Kotlin を学びましょう: コンセプト: 26. 依存関係の注入 Kotlin での依存関係の注入と Hilt の使用、シリーズ 2: 手動の依存関係の注入
この連載ブログでは主に以下の内容を紹介していきます。
Dependency Injection
(依存性注入) 概念の紹介。インターネット上で DI に関する多くの紹介を読みましたが、霧の中にいます。ここでは分かりやすくご紹介します。- 手動依存性注入の概要。Hilt を誰にでも理解しやすくするために、まず依存関係注入の効果を手動で実現する方法を紹介します。
- Hilt アノテーション (注釈) の導入と使用例
- MVVM ケースで Hilt を使用する方法
このブログは手動による依存関係の挿入に焦点を当てています。
記事ディレクトリ
レビュー1件
シリーズの最初のブログでは、依存性注入の概念とそれが必要な理由を紹介しました。
簡単に言うと、オブジェクト A がオブジェクト B の別のインスタンスを必要とする (依存する) ようなシナリオで、オブジェクトを作成するタスクを他のインスタンスに転送し、依存関係を直接使用することを依存関係注入 (DI) と呼びます。前回のブログでは、Car クラスをインスタンス化する例を取り上げて、この車のオブジェクトと、この車のオブジェクトが依存するさまざまなオブジェクト (車輪やエンジンなど) との間の結合を防ぐ方法を説明しました。
たとえば、次の例では、Car インスタンスが作成されると、新しい Wheel インスタンスが作成され、これが結合されます。DI の目的は、これを防ぐことです。
class Car {
<!-- -->
private val wheelA = Wheel()
fun start() {
<!-- -->
engine.start()
}
}
fun main(args: Array) {
<!-- -->
val car = Car()
car.start()
}
さらに、依存関係注入の 3 つの実装もリストしました。
- コンストラクター インジェクション (クラス コンストラクター インジェクション): 依存関係はクラス コンストラクターを通じて提供されます。
- セッター注入 (クラス フィールド インジェクション): クライアントは、インジェクターが依存関係を注入するために使用するセッター メソッドを公開します。
- インターフェイス注入: 依存関係は、それを渡すクライアントに依存関係を注入できるインジェクター メソッドを提供します。クライアントは、依存関係を受け入れるセッター メソッドを公開するインターフェイスを実装する必要があります。
2 柄の定義
Hilt provides a standard way to use DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically.
Hilt の定義から、Hilt は主に次の 2 つのことを実現すると結論付けることができます。
- さまざまな依存関係を保持するための「コンテナ」(コンテナ)を提供します。
- これらの「コンテナ」(コンテナ)の「ライフサイクル」を自動的に管理します。
次の章では、手動依存性注入を通じて上記 2 つの意味と重要性を説明します。
3 手動による依存関係の挿入: さまざまな依存関係を管理するコンテナーを提供します。
前回のブログと同様に、いくつかの例を使用して手動依存性注入の概念を説明します。
次の図は、MVVM の基本的なアーキテクチャです。
上の図の矢印は一方向であり、矢印の一端が他端に依存していることを意味します。たとえば、Activity/Fragment
に依存しViewModel
、ViewModel
に依存しますRepository
。ViewModel
Android の MVVM アーキテクチャでは、依存関係の注入は、(インスタンス) のインスタンスをクラスに注入することを意味しActivity/Fragment
、同じ理由で、 のRepository
インスタンスをViewModel
クラスに注入することも意味します。同様に、Model
とRemoteDataSource
のインスタンスもRepository
クラスに注入する必要があります。
実際、私たちが通常行うのは、Activity/Fragment
その中に新しいものを直接作成することですViewModel
。便利そうに見えますが、実は上記の結合例とよく似ていませんか?Activity/Fragment
1つと 1 つの dependしかない場合はViewModel
問題ありませんが、関係が複雑な場合、依存性注入の利点は明らかです。
たとえば、ユーザーログイン機能を実装する必要がある場合、MVVM アーキテクチャは次のようになります。
ここで、LoginActivity
に依存しLoginViewModel
、LoginViewModel
に依存しUserRepository
、に依存し、 にUserRepository
依存します。に依存します。UserLocalDataSource
UserRemoteDataSource
UserRemoteDataSource
Retrofit
依存性注入のアイデアを使用しない場合、LoginActivity
大まかに次のようになります。
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 实例化LoginViewModel
loginViewModel = LoginViewModel(userRepository)
}
}
のインスタンス化を満たすためには、のインスタンス パラメータLoginViewModel
を渡す必要があります。これはこれで終わりではなく、にも依存するためです。次から次へとリングが鳴る。したがって、完全に記述すると、大まかに次のようになります。UserRepository
userRepository
UserRepository
UserLocalDataSource
UserRemoteDataSource
LoginActivity
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
/******新增代码 Begin ******/
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
val remoteDataSource = UserRemoteDataSource(retrofit)
val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
/******新增代码 End ******/
loginViewModel = LoginViewModel(userRepository)
}
}
上記のコードの「新しいコード」セクションには、各依存関係間の連動関係が含まれています。したがって、LoginActivity
クラス内で、関連するすべての依存関係のインスタンスを作成します。しかし、私たちが当初想定していたように、LoginActivity
クラス内にオブジェクトがある限り、他のオブジェクトはクラスloginViewModel
内に存在する必要はありません。LoginActivity
必要なのは、次のようなきれいなコードです。
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
loginViewModel = XXXX
}
}
私たちができることは、新しいAppContainer
クラスを作成し、新しく追加されたすべてのコードをそこにスローすることです。
class AppContainer {
<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
クラス内のこれらの「依存関係」はアプリケーション (アプリケーション) 全体でグローバルに使用する必要があるため、新しく作成したAppContainer
クラスをクラス内で使用することはできません。そのため、クラスのインスタンスをのサブクラスに配置する必要があります。LoginActivity
AppContainer
AppContainer
Application()
MyApplication
を継承する新しいクラスを作成しますApplication()
。
class MyApplication : Application() {
<!-- -->
val appContainer = AppContainer()
}
このようにして、LoginActivity
次のように使用できます。
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
val appContainer = (application as MyApplication).appContainer
loginViewModel = LoginViewModel(appContainer.userRepository)
}
}
ここまでは、LoginActivity
依存関係注入のコーディング スタイルをクラスで確認できました。
ここで実装するオブジェクトはloginViewModel
、Constructor Injection: の依存性注入メソッドを使用しますloginViewModel = LoginViewModel(appContainer.userRepository)
。
LoginActivity
さらに、Android アプリケーションで、クラスに加えて、他のクラスもLoginViewModel
そのクラスのインスタンス オブジェクトを必要とする場合、LoginActivity
そのクラスの新しいLoginViewModel
インスタンス オブジェクトを作成することはできません。これは依然として古い方法であり、「依存関係」の 1 つとして、実装されたインスタンス オブジェクトを「コンテナ」(コンテナ) に配置する必要がありますLoginViewModel
。ここでは、ファクトリ パターンのデザイン パターンを使用し、新しいFactory
インターフェイスを作成し、LoginViewModelFactory
このインターフェイスをクラスに実装して、次LoginViewModel
のインスタンスを返す必要があります。
interface Factory<T> {
<!-- -->
fun create(): T
}
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
<!-- -->
override fun create(): LoginViewModel {
<!-- -->
return LoginViewModel(userRepository)
}
}
次は簡単です。この新しいLoginViewModelFactory
ファクトリ クラスを次のように配置しますAppContainer
。
class AppContainer {
<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
// 新建一个 loginViewModelFactory 对象,在整个application范围内都可以使用
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
その後、 (以前のように のLoginActivity
新しいインスタンス オブジェクトを作成する代わりに) クラス内で直接使用できます。LoginViewModel
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
val appContainer = (application as MyApplication).appContainer
//loginViewModel = LoginViewModel(appContainer.userRepository) 替换成下面代码:
loginViewModel = appContainer.loginViewModelFactory.create()
}
}
4 手動による依存関係の挿入: 手動によるライフサイクル管理
現在、多くの Android アプリケーションが複数のユーザーをサポートしているため、上記のアプリケーション機能を拡張する必要があります。つまり、さまざまなユーザーのログイン情報を記録する必要があります。LoginActivity
次の目標を達成するには、新しい関数を追加する必要があります。
- ユーザーのログイン中はクラス インスタンス オブジェクトへのアクセスを維持し
LoginUserData
、ログアウト後にリソースを解放します。 - 新しいユーザーがログインすると、新しい
LoginUserData
クラス インスタンス オブジェクトが作成されます。
このとき、クラスのインスタンスオブジェクトとそのクラスのインスタンスオブジェクトをLoginContainer
格納するクラスを追加する必要があります。(ヒント: 依存関係はコンテナー、つまりさまざまなクラスのインスタンス オブジェクトに配置されます)LoginUserData
LoginViewModelFactory
class LoginContainer(val userRepository: UserRepository) {
<!-- -->
val loginData = LoginUserData()
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
次に、LoginContainer
前の内容に適用しますAppContainer
。
class AppContainer {
<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
//val loginViewModelFactory = LoginViewModelFactory(userRepository)
// loginViewModelFactory 的实现已经放到LoginContainer中,此处不再需要
// 新建一个loginContainer变量,类型是LoginContainer,初始值是null
// 因为当用户退出时,其值时null
var loginContainer: LoginContainer? = null
}
LoginActivity
次に、クラスに戻り、LoginActivity
クラスのステージonCreate()
(ユーザーログイン)でインスタンスオブジェクトをloginContainer
取得しLoginUserData
、onDestroy()
ステージ (ユーザー出口) で対応するリソースを解放する必要があります。
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
private lateinit var loginData: LoginUserData
private lateinit var appContainer: AppContainer
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
appContainer = (application as MyApplication).appContainer
//loginViewModel = LoginViewModel(appContainer.userRepository) 替换成下面代码:
//loginViewModel = appContainer.loginViewModelFactory.create() 替换成下面代码:
// 用户登录,实例化LoginContainer,得到appContainer中的loginContainer 对象
appContainer.loginContainer = LoginContainer(appContainer.userRepository)
// loginViewModel 对象的获取比原来多了一层 loginContainer 对象
loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
loginData = appContainer.loginContainer.loginData
}
override fun onDestroy() {
<!-- -->
// 用户退出,释放资源
appContainer.loginContainer = null
super.onDestroy()
}
}
5 まとめ
ここでは、手動による依存関係の注入によって何が行われたかを要約します。
- まず、新しい
AppContainer
クラスを作成し、LoginViewModel
必要なすべての依存関係をそのクラスに追加します。 LoginActivity
アプリケーション内の他の場所 ( を除く)で のインスタンスを使用するにはLoginViewModel
、ファクトリ クラス設計パターンを使用してクラス コンテナにオブジェクトAppContainer
を実装します。loginViewModelFactory
- 最後に、異なるユーザーのログインとログアウトを実現するために、新しいクラス コンテナを作成してクラス コンテナに
LoginContainer
配置し、ユーザーのログイン情報を で取得し、ユーザーがログアウトした後、それを取得します。リリースリソース。AppContainer
LoginActivity
onCreate()
loginData
LoginActivity
onDestroy()
アプリケーション機能がますます複雑になると (現在はログイン機能の 1 つにすぎません)、手動による依存関係の注入は保守できなくなることが予測されます。これがヒルトが使用される理由です。