Kotlin 39. Kotlin での依存関係の注入と Hilt の使用、シリーズ 2: 手動の依存関係の注入

一緒に 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に依存しViewModelViewModelに依存しますRepositoryViewModelAndroid の MVVM アーキテクチャでは、依存関係の注入は、(インスタンス) のインスタンスをクラスに注入することを意味しActivity/Fragment、同じ理由で、 のRepositoryインスタンスをViewModelクラスに注入することも意味します。同様に、ModelRemoteDataSourceのインスタンスもRepositoryクラスに注入する必要があります。

実際、私たちが通常行うのは、Activity/Fragmentその中に新しいものを直接作成することですViewModel便利そうに見えますが、実は上記の結合例とよく似ていませんか?Activity/Fragment1つと 1 つの dependしかない場合はViewModel問題ありませんが、関係が複雑な場合、依存性注入の利点は明らかです。

たとえば、ユーザーログイン機能を実装する必要がある場合、MVVM アーキテクチャは次のようになります。

画像の説明を追加してください

ここで、LoginActivityに依存しLoginViewModelLoginViewModelに依存しUserRepository、に依存し、 にUserRepository依存しますに依存しますUserLocalDataSourceUserRemoteDataSourceUserRemoteDataSourceRetrofit

依存性注入のアイデアを使用しない場合、LoginActivity大まかに次のようになります。

class LoginActivity: Activity() {
    
    <!-- -->

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
    
    <!-- -->
        super.onCreate(savedInstanceState)
        // 实例化LoginViewModel
        loginViewModel = LoginViewModel(userRepository)
    }
}

のインスタンス化を満たすためには、のインスタンス パラメータLoginViewModelを渡す必要がありますこれはこれで終わりではなく、にも依存するためです次から次へとリングが鳴る。したがって、完全に記述すると、大まかに次のようになります。UserRepositoryuserRepositoryUserRepositoryUserLocalDataSourceUserRemoteDataSourceLoginActivity

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クラスをクラス内で使用することはできません。そのため、クラスのインスタンスをのサブクラスに配置する必要があります。LoginActivityAppContainerAppContainerApplication()

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格納するクラスを追加する必要があります。(ヒント: 依存関係はコンテナー、つまりさまざまなクラスのインスタンス オブジェクトに配置されます)LoginUserDataLoginViewModelFactory

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取得しLoginUserDataonDestroy()ステージ (ユーザー出口) で対応するリソースを解放する必要があります。

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配置し、ユーザーのログイン情報を で取得しユーザーがログアウトした後、それを取得します。リリースリソース。AppContainerLoginActivityonCreate()loginDataLoginActivityonDestroy()

アプリケーション機能がますます複雑になると (現在はログイン機能の 1 つにすぎません)、手動による依存関係の注入は保守できなくなることが予測されます。これがヒルトが使用される理由です。

おすすめ

転載: blog.csdn.net/zyctimes/article/details/129218481