Injeção de Dependência, uma breve introdução

Um dos maiores pesadelos de qualquer desenvolvedor é ter que inserir funcionalidades não previstas no escopo inicial do projeto, e perceber que sua arquitetura perfeita e modular não é tão perfeita assim.

Quem nunca olhou uma classe durante horas pensando: Como diabos eu vou acrescentar isso.  Ou ainda, tentar levar uma classe que julgava ser modular e quando vê teve que copiar metade do projeto, pois várias dependências não previstas apareceram.

Escrever código capaz de sobreviver bem a mudanças de escopo, modular de verdade está menos relacionado a inteligência e muito mais a experiência, cada um desses pequenos traumas que vamos desenvolvendo durante nossas carreiras nos torna capazes de ver os potenciais problemas por um diferente ângulo, e desenvolver passa a ser um jogo de xadrez, onde você tenta evitar cometer os mesmos erros anteriores.

Isso não é fácil.

Faz parte da vida do programador a busca constante por melhores estratégias de arquitetar seu código, a busca código perfeito digamos assim.

Numa dessas minhas jornadas encontrei um video muito interessante da Niantic, a produtora de Pokemon Go, e traz diversos pontos extremamente válidos sobre desenvolvimento de software. O principal ponto foi como eles utilizaram Injeção de Dependência  para resolver vários problemas que projetos maiores enfrentam ao utilizar o Unity, e exemplificam a incrível biblioteca Zenject.

Mas o que diabos Injeção de Dependência ?

*Para quem sabe inglês recomendo ler o material original do Zenject, de onde me baseei para criar os exemplos

Normalmente para realizarmos algum trabalho útil dentro de uma classe precisamos interagir com outras, imagine:

Não chega a ser um problema em pequenos projetos, porém a classe Obj está acoplada a classe Service, o que começa a ser um problema enorme conforme o projeto cresce, você pode necessitar de diversos serviços, a criação de Service pode alterar, e você precisa alterar Obj também, ou pior, o construtor de Service pode vir a ter uma nova dependência em outra classe, o que forçaria Obj a propagá-la.

Não demora muito e percebemos que seria muito mais interessante:

Com essa mudança simples a classe Obj diminui seu acoplamento com a classe Service, porém imagine uma classe que utiliza a nossa Obj, deveria se parecer com:

Isso também é um problema, além dos mesmos argumentos anteriores, estamos passando a responsabilidade de OtherObj escolher qual implementação da interface IService será utilizada, o que faríamos se ao invés de utilizar Service quiséssemos utilizar Service2?

Novamente poderíamos fazer algo como:

Aplicando essa lógica a demais classes, acabamos deslocamos a responsabilidade de escolher qual implementação utilizar para cada vez mais alto no grafo de classes, em algum momento teríamos algo como:

Se levarmos isso ao extremo, teremos o caso onde antes mesmo do programa iniciar seu trabalho, todas as dependências terão sido resolvidas.

Bibliotecas de DI (Dependency Injection) como o Zenject automatizam esse processo.

Zenject

Caso utilizássemos o Zenject, poderíamos reescrever o exemplo como:

Dessa forma,  as classes não precisam se preocupar com a criação dos objetos e suas dependências, o que cria um padrão mais saudável conforme o projeto cresce. Inclusive, um bom critério para dividir uma classe é quando ela começa a ter dependências demais, mas isso é assunto para outro post.

Outro benefício não abordado aqui, é que ao utilização Injeção de Dependência, tornamos o código amigável a testes unitários, já que podemos substituir as dependências por mocks (objetos falsos) de forma a testar o código eliminando as dependências externas. Também será abordado num próximo artigo.

Agora que já temos uma ideia do que é Injeção de Dependência, num próximo artigo vou entrar mais em detalhes sobre como utilizar Zenject em um código um pouco maior!

Por enquanto é isso!

Siga e compartilhe!