engenharia de software - Quais são os principios do mecanismo de injeção de dependência? - Stack Overflow em Português
Já faz um tempo que eu tento compreender o mecanismo de injeção de dependência e sua principal essência, porem, isso não entra na minha mente, talvez seja por não compreender os fundamentos e princípios básicos primeiro xD. Sendo que eu sempre vejo que para facilitar nos testes unitários, o ideal seria utilizar injeção de dependência, isso me faz pensar se existe outras formas como o SRP para alcançar os mesmos objetivos em relação a testes.
Sendo assim, vou ilustrar um exemplo de código que tenta usar injeção de dependência através de uma biblioteca do Python chamada inject para que possa servir de cenário paras as dúvidas que virão, segue o código abaixo:
def d1():
print('dependência 1')
def d2():
print('dependência 2')
def dTest():
print('dependência teste')
def depedencias(binder):
binder.bind('dum', d1)
binder.bind('ddois', d2)
binder.bind('dtest', dTest)
inject.clear_and_configure(depedencias)
def funcao_tarefa(d):
dpendencia = inject.instance(d)
dpendencia()
funcao_tarefa('dum')
funcao_tarefa('ddois')
funcao_tarefa('dtest')
dependência teste
Com base no exemplo de código citado acima para contextualizar, eu gostaria que as minhas dúvidas fossem respondidas.
Qual seria o principal mecanismo por trás da injeção de dependência?
O que seria uma dependência e uma injeção?
O exemplo acima faz uso de injeção de dependência ou a injeção de dependência só é possível com orientação a objetos?
Injeção de dependência serve para diminuir o acoplamento de código e deixar o código mais testável?
E quais seriam os contras da injeção de dependência?
A finalidade desta perguntas é trazer respostas canônicas no qual clarifica essas dúvidas acima e se possível, ilustrar nas respostas com exemplos de códigos.
editada 14/03/21 às 20:03
perguntada 14/03/21 às 18:01
Relacionado (ou dup parcial): pt.stackoverflow.com/q/21319/101, pt.stackoverflow.com/q/195174/101, pt.stackoverflow.com/q/396507/101 e pt.stackoverflow.com/q/86484/101
14/03/21 às 19:32
São muitas perguntas e acho melhor explicar de uma forma geral e mais didática ao invés de responder uma a uma.
Considere os dois códigos abaixo:
database = MySQL()
return database.find()
def findAll(database):
findAll(database)
Nos dois códigos, temos uma dependência com o banco de dados. No primeiro, a instância do banco de dados é criada dentro da função. No segundo, a instância é passada como argumento da função, ou seja, injetada.
Este é o princípio da injeção de dependência.
A injeção de dependência é uma técnica importante pois permite que o objeto utilizado na função possa ser substituído por outro sem que esta função seja alterada.
No exemplo acima, caso seja necessário alterar o banco MySQL por um SQLite, sem injeção de dependência, teremos que alterar o código da função findAll.
database = SQLite()
Usando injeção de dependência, só precisamos alterar o valor dos argumentos da função
Importante notar que, para uma injeção de dependência funcionar, devemos utilizar os mecanismos de abstração da linguagem escolhida. Os objetos utilizados devem possuir a mesma interface, ou no caso de linguagens não tipadas, devem conter todas as funções utilizadas na função. No nosso caso, todos os objetos devem conter a função find().
Logo, a injeção de dependência permite criar sistemas desacoplados e configuráveis de forma transparente para a aplicação.
Esta facilidade é extremamente importante nos testes, mais especificamente na utilização de mocks e stubs. No primeiro caso, como a dependência ao MySQL está definida dentro da função, não é possível alterar o banco de dados. Isso é muito ruim para os testes, pois não podemos executar testes em ambientes reais. No segundo caso, utilizando injeção de dependências, basta mudar o valor do argumento da função para um mock.
mockedDatabase = Mock()
findAll(mockedDatabase)
Para finalizar, vamos a algumas observações referentes aos seus questionamentos:
SRP e injeção de dependência andam juntos. Não é responsabilidade da função ou classe criar uma instância do objeto MySQL, por exemplo.
Não precisamos de uma linguagem orientada a objetos para ter injeção de dependência, mas precisamos de mecanismos de abstração que são comuns a estas linguagens.
Não precisamos de bibliotecas ou frameworks para termos injeção de dependências. Elas apenas facilitam a nossa vida em sistemas complexos.
Sobre desvantagens, por ser um método um pouco mais trabalhoso, talvez não valha a pena em programas muito simples, sem probabilidade de crescimento.
Espero ter esclarecido suas dúvidas :-)
editada 15/03/21 às 13:48
respondida 14/03/21 às 19:40
José A. R. Santos JrJosé A. R. Santos Jr
Acho que essa é a a primeira pergunta a dar base ao tema.
Dependência é qualquer código (classe, biblioteca, método, etc) que um outro código necessita para fazer usa tarefa, ou cumprir seu propósito.
Imagine que um código precise fazer a soma de dois valores e retornar esse valor. Vamos colocar isso num método (vou usar C# nos exemplos):
int Somar(int num1, int num2)
Este código pode fazer tudo que era necessário usando o operador de soma +, apenas usando dois parâmetro de tipos básicos, int.
Agora vamos imaginar que, para fazer isso o método receba dois números decimais, mas quer retornar um valor arredondado em 2 casas. Para vai usar uma classe que já faz isso:
double Somar(double num1, double num2)
AlgumaClasse objetoQueCalcula = new AlgumaClasse();
return objetoQueCalcula.Arredondar(num1 + num2, 2);
Neste caso, usei um código "externo" e esse pequeno pedaço de código, AlgumaClasse, e criei uma dependência com esse outro código. Veja que, esse próprio código sabe dessa dependência, e ele mesmo resolveu como "obter" uma instância dessa classe, criando seu próprio objeto ao fazer AlgumaClasse objetoQueCalcula = new AlgumaClasse();
Injeção seria uma implementação onde, no lugar do método Somar conhecer e ele mesmo contruir a dependência que precisa, ele receber essa implementação já pronta, assim:
double Somar(double num1, double num2, AlgumaClasse objetoQueCalcula)
Agora o método só precisa se preocupar com o que ele precisa fazer, deixando a cargo que quem for utilizar, fornecer tudo que ele necessita.
Aqui é apenas um exemplo básico, se passarmos o tipo da classe vamos cair num outro problema de acomplamento que veremos mais adiante.
O objetivo do mecanismo é deixar um código independente da criação das suas dependências. Aqui no SOPt temos essa pergunta relacionada: O que é injeção de dependência?.
Aproveitando as boas respostas, aqui algumas definições:
Injeção de Dependências é um tipo de Inversão de Controle e significa que uma classe não mais é responsável por criar ou buscar os objetos dos quais depende.
É um Design Pattern que prega um tipo de controle externo, um container, uma classe, configurações via arquivo, etc., inserir uma dependência em uma outra classe.
Ou seja, o controle sobre a criação das dependências é passado para outro código responsável por isso, um código que saiba criar/fornecer as instâncias que outros códigos vão precisar. Na outra pergunta foi usado o termo container, que não tem relação com containers do Docker. Este container conhece as dependências que são necessárias e as criar/injeta nos códigos. Vamos usar esse nome, sem nos preocupara exatamente com o conceito de container.
Aqui há um ponto importante:
Interfaces são contratos que as classes devem implementar, ou seja, descreve os membros (métodos, propriedades) que uma classe deve ter. O que as interfaces tem a ver com a injeção? Ao inveter o controle da criação das dependências, resolvemos alguns problemas de acomplamento e instâncias (falaremos mais adiante), mas ao passar a classe, criamos outro problema de acomplamento: se eu precisar mudar a classe em si, usar outra no lugar, mudar seu nome, etc, também será necessário mudar o código que depende dessa classe, o que é um problema.
No exemplo acima, se a classe AlgumaClasse mudasse de nome, ou precisasse usar alguma outra classe semelhante para substituí-la (falaremos disso adiante), seria necessário mudar todos os métodos que usam essa classe, e se hover outros códigos quem usem essa dependêcia, provelmente todos deverão ser alterados. Agora vejamos essa implementação com interfaces:
interface IAlgumaCoisa
double Arredondar(double num1, double num);
Aqui criamos um contrato dizendo que "Quem usar IAlgumaCoisa vai ter um método Arredondar, que devolve um double e recebe dois números double". Isso é exatamente o que o nosso pequeno código precisa, mudando a implementação ficará:
double Somar(double num1, double num2, IAlgumaClasse objetoQueCalcula)
O código fica o mesmo, mas depende agora menos ainda da classe em si, pois ele só conhece o contrato. Poderíamos ter:
public class AlgumaClasse: IAlgumaClasse
public double Arredondar(double num1, double num)
... faz alguma coisa ..
public class AlgumaOutraClasseQualquer: IAlgumaClasse
Poderíamos usar qualquer uma das duas classes, pois para o método Somar, desde que seja respeitado o conceito, pouco importa.
Agora voltando ao mecanismo em si.
Assim, o container, o qualquer outro nome que se queira dar, pode registrar essas dependências e ele mesmo injetar isso no código, algo assim por exemplo:
Container.RegistrarDependencia(IAlgumaClasse, AlgumaOutraClasseQualquer);
Ou seja, "quando alguém precisar de IAlgumaClasse, use AlgumaOutraClasseQualquer.
Para fazer isso, existe várias bibliotecas, pacotes, etc que já fazem isso. Em .Net temos o IServiceProvider e outros pacotes como SimpleInjector, Ninject e Unity, todos eles implementam o mecanismo de injeção, o que se precisa fazer é, registrar as interfaces e qual classe, ou como construir um objeto que implemente aquela interface. Esses motores de injeção de dependência trabalham com interfaces, por isso abordei o assunto antes.
O exemplo de código da pergunta é:
Ou seja, o método funcao_tarefa recebe um parâmetro "d" e usa um código externo (container, provider, qualquer que seja a definição) que, a partir de um registro prévio das dependências, fornece a dependência que o método necessita, logo isso pode ser considerado como injeção de dependência.
Sim, esse é o objetivo. Ao remover o acomplamento, deixamos o código independente da criação das suas dependências. Isso como dito anteriormente, faz com que mudanças nas dependências, com tanto que respeitados os contratos, não vão necessitar necessariamente em alteração no código que usa as dependências.
Mas temos mais um ponto muito importante: testes.
Código altamente acoplado é difícil de ser testado. Imagine um código por exemplo que usa um banco de dados para obter dados e executar alguma tarefa:
public bool AutenticarUsuario(string usuario, string senha)
BancoDeDados bd = new BancoDeDados();
Usuario usr = bd.ObterUsuario(usuario);
return usr != null && usr.Senha == senha;
Ao escrever um teste unitário, que deveria testar só essa unidade básica, ou seja, "dado um usuário/senha, obter o usuário de um banco de dados e validar se exite e a senha é a mesma informada", como fazer sem usar um banco de dados real? Não seria possível.
Agora se esse código fosse assim:
public bool AutenticarUsuario(string usuario, string senha, IBancoDeDados bd)
Assim, eu poderia, como no exemplo anterior, criar uma implementação de IBancoDeDados, que não fosse um banco de dados, controlar o que é retornado (null, usuário com senha correta ou não) e testar o comportamente desse método, técnica que é comumente conhecida como Mock, ou criar um objeto que tenha o comportamento controlado para o teste, e em seguida registrar esse "Mock" no controlador de dependências:
Container.Registrar(IBancoDeDados, MinhaClasseMock);
Ou, num exemplo um pouco mais real:
Container.Add<IBancoDeDados, Mock<IBancoDeDados>>();
Ou seja, injeção de dependência diminui o acoplamento, o que permite além de tornar o código independente da criação de suas dependências, mais facilmente "testável".
Como vimos nos exemplos de código, é preciso implementar mais código:
Registrar todas as dependências (algumas podem não ser tão simples, e depender de outras ou de condições específicas, o que dificulta o registro);
Para aproveitar ao máximo, devemos separar os contratos da implementação das classes, o que resulta em mais código;
Para não precisar fazer "tudo na mão", necessário usar bibliotecas/pacotes/frameworks que gerenciem a injeção de dependência.
Mas ao comparar ao benefícios, parece, ao menos na minha opinião, ser muito mais vantajoso.
Para demonstrar isso, criei um pequeno código, mas infelizmente o dotnetfiddle da erro ao usar o package do Moq, e o ideone não aceita packages :(
Mas quem quiser ver ou testar local está aqui: https://dotnetfiddle.net/Sg2VMW
editada 31/08/21 às 17:56
respondida 14/03/21 às 19:24
Excelente resposta. A única coisa que eu teria a adicionar é que a implementação do OP tem cara e cheiro de Service Locator, algo que muitos consideram um anti-pattern (por, entre outras coisas, amarrar a implementação ao framework, esconder o contrato das dependências e abrir espaço para vulnerabilidade em tempo de execução). Composição de funções é um assunto abrangente e existem técnicas que trazem muitos dos benefícios da injeção de dependência (e.g. aplicação parcial de funções, reader monads, etc). Não vou entrar no mérito sobre o que é ou deixa de ser injeção de dependênca :).
14/03/21 às 20:50
ótimos pontos @AnthonyAccioly, se pensarmos em DI como "uma forma da classe não ser mais responsável por explicitamente criar as instâncias de suas pedendências", se classe usar uma factory por exemplo para isso, que abstraia a criação das instâncias, seria muito próximo desse conceito :) é bem interessante pensar nas formas de implementar. Eu tentei detalhar alguns pontos e por alguns exemplos, e tbm tentar responder todos os questionamentos da pergunta, espero que ajude a comunidade
14/03/21 às 23:01
Esta não é a resposta que você está procurando? Pesquise outras perguntas com a tag engenharia-de-software injeção-de-dependência independente-de-linguagem ou faça sua própria pergunta.