Executando verificação de segurança...
4

Não é mágica 🪄 | Gerenciamento de Dependências Cíclicas

Enquanto escrevia uma aula sobre Injeção de Dependência, lembrei de uma dúvida que tive no meio da minha carreira e que é muito comum entre programadores Plenos: Como resolver dependências cíclicas?

Eu digo entre "Plenos" porque é nesse momento que começamos a questionar como (DI) funcionam, enquanto ainda acreditamos que muita coisa é feita magicamente. Enfim!

Gerenciamento de dependências cíclicas é a capacidade de lidar com situações em que uma classe depende de outra classe, que por sua vez, depende da primeira classe. wow!

Isso pode ocorrer quando duas ou mais classes precisam ser instanciadas em conjunto e são dependentes umas das outras.

Exemplo de dependência cíclica:

class A {
    lateinit var b: B
    fun foo() {...}
}
class B {
    lateinit var a: A
    fun bar() {...}
}

Esse tipo de situação pode causar um loop infinito, onde a biblioteca que tu usa para fazer (DI) fica tentando resolver as dependências, ou pode causar erros em tempo de execução.

A maioria das bibliotecas de (DI), como o Dagger por exemplo para JAVA, tem suporte para gerenciar dependências cíclicas. E isso geralmente é feito através da definição de "providers" para as dependências cíclicas.

Um provider é uma função que fornece uma instância de uma dependência, que é chamada apenas quando a dependência é realmente necessária.

Isso permite que as dependências cíclicas sejam resolvidas de forma eficiente e evita problemas em tempo de execução.

class CyclicModule {

    fun provideA(b: Provider<B>): A {
        val a = A()
        a.b = b.get()
        return a
    }

    fun provideB(a: Provider<A>): B {
        val b = B()
        b.a = a.get()
        return b
    }
}

Isso permite que a biblioteca de injeção de dependência crie uma instância de A e B separadamente, sem entrar em um loop infinito. 👍

Carregando publicação patrocinada...
1

Cara, eu fiquei com algumas dúvidas, provavelmente porque eu conheço muito pouco de Kotlin, mas acho que você pode me ajudar a esclarecer algumas coisas.

A princípio, me parece que o ponto principal da solução pra lidar com dependências cíclicas é na verdade esse lateinit, uma vez que ele trabalha com essa semântica da gente não precisar inicializar esses caras na construção do objeto, tanto que, eu fiz um teste aqui com o playground do Kotlin, fazendo DI "manual" (sem container automático), e dá certo, se liga:

class A {
    lateinit var b: B
    
    fun foo() {
        b.bar();
    }
    
    fun bar() {
        println("A!");
    }
}

class B {
    lateinit var a: A
    
    fun foo() {
        a.bar();
    }
    
    fun bar() {
        println("B!");
    }
}


fun main() {
    val a = A();
    val b = B();
    
    a.b = b;
    b.a = a;
    
    a.foo();
    b.foo();
}

Daí eu queria saber aonde entram os providers nessa história e, como seria uma implementação barebones deles.

1

O lateinit apenas diz que uma val nao nula vai ser inicializada em algum momento. Já o provider funciona como uma inicializacao lazy.

manualmente tu consegue controlar qual tu esta instanciando primeiro mas nem sempre tu consegue ou sabe qual vai ser instanciado primeiro (como ao usar uma lib de DI)