Kotlin 39. Injeção de dependência e o uso do Hilt em Kotlin, Série 2: Injeção de dependência manual

Vamos aprender Kotlin juntos: Conceito: 26. Injeção de dependência Injeção de dependência e o uso do Hilt em Kotlin, Série 2: Injeção manual de dependência

Nesta série de blogs, apresentaremos principalmente:

  • Dependency Injection(Injeção de Dependência) Introdução ao conceito. Eu li muitas introduções sobre DI na Internet, mas estou confuso. Aqui, nós o apresentamos de uma maneira fácil de entender.
  • Uma introdução à injeção de dependência manual. Para tornar mais fácil para todos entenderem o Hilt, primeiro apresentamos como obter efeitos de injeção de dependência manualmente.
  • Hilt annotations (anotações) introdução e casos de uso
  • Como usar o Hilt no caso MVVM

Este blog se concentra na injeção de dependência manual.



1 avaliação

No primeiro blog da série, apresentamos o conceito de injeção de dependência e por que ela é necessária.

Em termos simples, em um cenário como o objeto A precisa (depende de) outra instância do objeto B, transferir a tarefa de criar objetos para outros e usar dependências diretamente é chamado de injeção de dependência (DI). No blog anterior, também pegamos o exemplo de instanciar a classe Car para explicar como evitar o acoplamento entre esse objeto car e vários objetos dos quais ele depende, como rodas e motores.

Por exemplo, no exemplo a seguir, quando a instância Car for criada, uma nova instância Wheel será criada, que é o acoplamento. O objetivo do DI é evitar que isso aconteça.

class Car {
    
    <!-- -->
    private val wheelA = Wheel()

    fun start() {
    
    <!-- -->
        engine.start()
    }
}

fun main(args: Array) {
    
    <!-- -->
    val car = Car()
    car.start()
}

Além disso, também listamos três implementações de injeção de dependência:

  • injeção de construtor (injeção de construtor de classe): as dependências são fornecidas por meio do construtor de classe.
  • injeção de setter (injeção de campo de classe): o cliente expõe um método setter que o injetor usa para injetar dependências.
  • injeção de interface: Dependency fornece um método injetor que pode injetar dependências em qualquer cliente que o passe. Os clientes devem implementar uma interface que exponha um método setter que aceite dependências.

2 Definição de punho

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.

A partir da definição de Hilt, podemos concluir que Hilt realiza principalmente duas coisas:

  • Fornece "containers" (contêineres) para armazenar várias dependências;
  • Gerenciar automaticamente os "ciclos de vida" desses "containers" (containers).

Nos capítulos seguintes, explicaremos o significado e importância das duas coisas acima por meio da injeção manual de dependência.

3 Injeção de dependência manual: forneça um contêiner para gerenciar várias dependências

Como no blog anterior, também usamos alguns exemplos para explicar o conceito de injeção manual de dependência.

A figura a seguir é uma arquitetura básica do MVVM:

Adicione uma descrição da imagem

A flecha na figura acima é unidirecional, o que significa que uma ponta da flecha depende da outra ponta. Por exemplo, Activity/Fragmentdepende de ViewModele ViewModeldepende de Repository. Na arquitetura MVVM do Android, injeção de dependência significa injetar ViewModelinstâncias de (instância) em Activity/Fragmentclasses e, pelo mesmo motivo, Repositoryinjetar instâncias de dentro de ViewModelclasses. Por analogia, instâncias de Modele também precisam ser injetadas na classe.RemoteDataSourceRepository

Na verdade, o que costumamos fazer é Activity/Fragmentcriar diretamente um novo nele ViewModel. Parece conveniente, mas, na verdade, não é muito semelhante ao exemplo de acoplamento acima? Se tivermos apenas um Activity/Fragmente um dependente ViewModel, tudo bem, mas se o relacionamento for complicado, as vantagens da injeção de dependência são óbvias.

Por exemplo, quando for necessário implementar uma função de login do usuário, a arquitetura MVVM deve ser a seguinte:

Adicione uma descrição da imagem

Aqui, LoginActivitydepende de LoginViewModel, LoginViewModeldepende de UserRepository, UserRepositorydepende de UserLocalDataSourcee UserRemoteDataSource. UserRemoteDataSourcedepende de Retrofit.

Quando não estiver usando a ideia de injeção de dependência, LoginActivitydeve ficar mais ou menos assim:

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

    private lateinit var loginViewModel: LoginViewModel

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

Para satisfazer LoginViewModela instanciação de , precisamos passar um UserRepositoryparâmetro de instância de userRepository. Isso não acaba aí, porque UserRepositorypor sua vez depende UserLocalDataSourcetambém UserRemoteDataSource. Um toque após o outro. Então, se escrevermos LoginActivitycompletamente, ficará mais ou menos assim:

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)
    }
}

A seção "novo código" no código acima contém o relacionamento de intertravamento entre cada dependência. Assim, na LoginActivityclasse, criamos instâncias de todas as dependências relacionadas. Mas como imaginamos originalmente, LoginActivitydesde que existam objetos na classe loginViewModel, os outros não precisam aparecer na LoginActivityclasse. O que precisamos é algo como este código limpo:

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

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
    
    <!-- -->
        super.onCreate(savedInstanceState)
        // 获取 loginViewModel 对象
        loginViewModel = XXXX
    }
}

O que podemos fazer é criar uma nova AppContainerclasse e jogar todo o código recém-adicionado nela:

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)
}

A AppContainerclasse recém criada não pode ser LoginActivityutilizada na classe, pois AppContaineressas "dependências" na classe precisam ser utilizadas globalmente em toda a aplicação (aplicação), então a AppContainerinstância da classe precisa ser colocada Application()na subclasse de:

Criamos uma MyApplicationnova classe que herda de Application():

class MyApplication : Application() {
    
    <!-- -->
    val appContainer = AppContainer()
}

Dessa forma, LoginActivitypode ser usado assim em:

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)        
    }
}

Até agora, LoginActivityo estilo de codificação da injeção de dependência pode ser visto na classe.

O objeto que implementamos aqui loginViewModelusa o método de injeção de dependência do Constructor Injection: loginViewModel = LoginViewModel(appContainer.userRepository).

Além disso, se em nosso aplicativo Android, além LoginActivityda classe, outras classes também precisarem LoginViewModelde objetos de instância da classe, não poderemos criar novos objetos de instância LoginActivityda classe . LoginViewModelAinda é o jeito antigo, como uma das "dependências", precisamos colocar os LoginViewModelobjetos de instância implementados em "containers" (containers). Aqui, você precisa usar o padrão de design do padrão de fábrica, criar uma nova Factoryinterface, LoginViewModelFactoryimplementar essa interface na classe e retornar uma LoginViewModelinstância de:

interface Factory<T> {
    
    <!-- -->
    fun create(): T
}

class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
    
    <!-- -->
    override fun create(): LoginViewModel {
    
    <!-- -->
        return LoginViewModel(userRepository)
    }
}

A próxima coisa é simples, colocamos esta nova LoginViewModelFactoryclasse de fábrica em 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)  
}

Em seguida, LoginActivityele pode ser usado diretamente na classe (em vez de criar um novo LoginViewModelobjeto de instância como antes):

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 Injeção Manual de Dependência: Gerenciamento Manual do Ciclo de Vida

Agora, muitos aplicativos Android oferecem suporte a vários usuários, portanto, precisamos expandir as funções do aplicativo acima: registrar diferentes informações de login do usuário. Isso requer LoginActivitya adição de novas funções para atingir os seguintes objetivos:

  • Mantenha o acesso ao objeto de instância da classe durante o login do usuário LoginUserDatae libere o recurso após o logout;
  • Quando um novo usuário fizer login, crie um novo LoginUserDataobjeto de instância de classe.

Neste momento, precisamos adicionar uma LoginContainerclasse para armazenar LoginUserDataos objetos de instância da classe e LoginViewModelFactoryos objetos de instância da classe. (DICAS: As dependências são colocadas no container, ou seja, os objetos instância de várias classes)

class LoginContainer(val userRepository: UserRepository) {
    
    <!-- -->

    val loginData = LoginUserData()

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

Em seguida, LoginContaineraplique ao anterior 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 
}

Em seguida, de volta à LoginActivityclasse, precisamos obter o objeto de instância no estágio LoginActivityda classe onCreate()(login do usuário) e liberar os recursos correspondentes no estágio (saída do usuário):loginContainerLoginUserDataonDestroy()

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 resumo

Aqui, resumimos o que a injeção de dependência manual fez:

  • Primeiro, crie uma nova AppContainerclasse e coloque LoginViewModelnela todas as dependências necessárias;
  • Para usar instâncias de em outros locais no aplicativo (exceto LoginActivity) LoginViewModel, usamos o padrão de design de classe fábrica AppContainerpara implementar um loginViewModelFactoryobjeto no contêiner de classe;
  • Por fim, para realizar o login e logout de diferentes usuários, criamos um novo LoginContainercontêiner de classe e o colocamos AppContainerno contêiner de classe e, em seguida, LoginActivitypodemos onCreate()obter as informações de login do usuário em loginData, e depois que o usuário faz logout, ele está em LoginActivityos onDestroy()recursos de liberação.

É previsível que, quando nossas funções de aplicativo se tornarem cada vez mais complexas (agora é apenas uma das funções de login), a injeção manual de dependência se tornará insustentável. É por isso que o Hilt é usado.

Acho que você gosta

Origin blog.csdn.net/zyctimes/article/details/129218481
Recomendado
Clasificación