Críticas e desvantagens da injeção de dependência

118

A injeção de dependência (DI) é um padrão bem conhecido e moderno. A maioria dos engenheiros conhece suas vantagens, como:

  • Tornando possível / fácil o isolamento em testes de unidade
  • Definindo explicitamente dependências de uma classe
  • Facilitar o bom design ( princípio de responsabilidade única (SRP), por exemplo)
  • Habilitando a implementação de alternância rapidamente (em DbLoggervez de ConsoleLoggerpor exemplo)

Eu acho que existe um consenso em todo o setor de que o DI é um padrão bom e útil. Não há muitas críticas no momento. As desvantagens mencionadas na comunidade são geralmente menores. Alguns deles:

  • Maior número de classes
  • Criação de interfaces desnecessárias

Atualmente, discutimos o projeto de arquitetura com meu colega. Ele é bastante conservador, mas de mente aberta. Ele gosta de questionar coisas que considero boas, porque muitas pessoas em TI apenas copiam a tendência mais recente, repetem as vantagens e, em geral, não pensam muito - não analisam muito profundamente.

As coisas que eu gostaria de perguntar são:

  • Devemos usar injeção de dependência quando temos apenas uma implementação?
  • Deveríamos proibir a criação de novos objetos, exceto os de linguagem / estrutura?
  • Injetar uma única implementação é uma péssima idéia (digamos que temos apenas uma implementação para não querermos criar uma interface "vazia") se não planejamos testar uma unidade em particular em uma classe específica?
Landeeyo
fonte
33
Você está realmente perguntando sobre a injeção de dependência como padrão ou está perguntando sobre o uso de estruturas de DI? Essas são coisas realmente distintas. Você deve esclarecer em que parte do problema está interessado ou perguntar explicitamente sobre as duas coisas.
Frax
10
@Frax sobre padrão, não quadros
Landeeyo
10
Você está confundindo inversão de dependência com injeção de dependência. O primeiro é um princípio de design. A última é uma técnica (geralmente implementada com uma ferramenta existente) para construir hierarquias de objetos.
Jpmc26
3
Costumo escrever testes usando o banco de dados real e sem objetos simulados. Funciona muito bem em muitos casos. E então você não precisa de interfaces na maioria das vezes. Se você tem uma UserServiceclasse que é apenas um detentor de lógica. Ele recebe uma conexão com o banco de dados e os testes são executados dentro de uma transação que é revertida. Muitos chamariam isso de má prática, mas descobri que isso funciona muito bem. Não é necessário contorcer seu código apenas para teste e você obtém o erro ao encontrar o poder dos testes de integração.
usr
3
O DI é quase sempre bom. O lado ruim disso é que muitas pessoas pensam que conhecem o DI, mas tudo o que sabem é como usar uma estrutura estranha, sem sequer ter certeza do que estão fazendo. Atualmente, o DI sofre muito com a programação de cultos de carga.
T. Sar

Respostas:

160

Primeiro, gostaria de separar a abordagem de design do conceito de frameworks. A injeção de dependência em seu nível mais simples e mais fundamental é simplesmente:

Um objeto pai fornece todas as dependências necessárias ao objeto filho.

É isso aí. Observe que nada disso exige interfaces, estruturas, qualquer estilo de injeção, etc. Para ser justo, aprendi sobre esse padrão pela primeira vez há 20 anos. Isso não é novo.

Devido ao fato de mais de duas pessoas terem confusão sobre o termo pai e filho, no contexto da injeção de dependência:

  • O pai é o objeto que instancia e configura o objeto filho que ele usa
  • A criança é o componente projetado para ser instanciado passivamente. Ou seja, ele foi projetado para usar quaisquer dependências fornecidas pelo pai e não instancia suas próprias dependências.

A injeção de dependência é um padrão para a composição do objeto .

Por que interfaces?

As interfaces são um contrato. Eles existem para limitar o quão bem dois objetos podem ser acoplados. Nem toda dependência precisa de uma interface, mas elas ajudam na escrita de código modular.

Quando você adiciona o conceito de teste de unidade, pode ter duas implementações conceituais para qualquer interface: o objeto real que você deseja usar em seu aplicativo e o objeto zombado ou stub que você usa para testar o código que depende do objeto. Isso por si só pode ser justificativa suficiente para a interface.

Por que estruturas?

Essencialmente, inicializar e fornecer dependências para objetos filhos pode ser assustador quando há um grande número deles. As estruturas fornecem os seguintes benefícios:

  • Dependências de ligação automática aos componentes
  • Configurando os componentes com configurações de algum tipo
  • Automatizando o código da placa da caldeira para que você não precise vê-lo escrito em vários locais.

Eles também têm as seguintes desvantagens:

  • O objeto pai é um "contêiner" e não nada no seu código
  • Torna o teste mais complicado se você não puder fornecer as dependências diretamente no seu código de teste
  • Ele pode retardar a inicialização, pois resolve todas as dependências usando reflexão e muitos outros truques
  • A depuração em tempo de execução pode ser mais difícil, principalmente se o contêiner injetar um proxy entre a interface e o componente real que implementa a interface (a programação orientada a aspectos incorporada ao Spring vem à mente). O contêiner é uma caixa preta e nem sempre é construído com o conceito de facilitar o processo de depuração.

Tudo isso dito, existem compensações. Para pequenos projetos em que não há muitas partes móveis e há poucas razões para usar uma estrutura de DI. No entanto, para projetos mais complicados, onde já existem determinados componentes para você, a estrutura pode ser justificada.

Que tal [artigo aleatório na Internet]?

Que tal isso? Muitas vezes as pessoas podem ficar com excesso de zelo e adicionar um monte de restrições e repreendê-lo se você não estiver fazendo as coisas da "maneira única". Não existe um caminho verdadeiro. Veja se você pode extrair algo útil do artigo e ignorar as coisas com as quais não concorda.

Em resumo, pense por si mesmo e experimente as coisas.

Trabalhando com "cabeças velhas"

Aprenda o máximo que puder. O que você encontrará com muitos desenvolvedores que estão trabalhando nos anos 70 é que eles aprenderam a não ser dogmático sobre muitas coisas. Eles têm métodos com os quais trabalham há décadas que produzem resultados corretos.

Tive o privilégio de trabalhar com alguns deles, e eles podem fornecer um feedback brutalmente honesto que faz muito sentido. E, quando vêem valor, acrescentam essas ferramentas ao seu repertório.

Berin Loritsch
fonte
6
@CarlLeth, eu trabalhei com várias estruturas, desde as variantes Spring até .net. O Spring permitirá que você injete implementações diretamente em campos particulares usando alguma magia negra de reflexão / carregador de classe. A única maneira de testar componentes criados dessa maneira é usar o contêiner. O Spring possui corredores JUnit para configurar o ambiente de teste, mas é mais complicado do que configurar você mesmo. Então, sim, eu apenas dei um exemplo prático .
Berin Loritsch
17
Há mais uma desvantagem que considero um obstáculo ao DI através de estruturas quando estou usando meu chapéu de solucionador de problemas / mantenedor: a ação assustadora à distância que eles fornecem dificulta a depuração offline. Na pior das hipóteses, eu tenho que executar o código para ver como as dependências são inicializadas e transmitidas. Você menciona isso no contexto de "testing", mas na verdade é muito pior se você está apenas começando a olhar para a fonte, nunca mente tentando fazê-lo funcionar (o que pode envolver uma tonelada de configurações). Impedir minha capacidade de dizer o que o código faz apenas olhando para ele é uma coisa ruim.
Jeroen Mostert
1
As interfaces não são contratos, são simplesmente APIs. Os contratos implicam semântica. Esta resposta está usando terminologia específica de linguagem e convenções específicas de Java / C #.
Frank Hileman
2
@BerinLoritsch O ponto principal de sua própria resposta é que o princípio de DI! = Qualquer estrutura de DI fornecida. O fato de o Spring poder fazer coisas terríveis e imperdoáveis ​​é uma desvantagem do Spring, não das estruturas de DI em geral. Uma boa estrutura de DI ajuda a seguir o princípio de DI sem truques desagradáveis.
Carl Leth
1
@CarlLeth: todas as estruturas DI são projetadas para remover ou automatizar algumas coisas que o programador não deseja explicar, elas variam apenas de como. De acordo com o meu conhecimento, todos eles eliminam a capacidade de saber como (ou se ) as classes A e B interagem apenas olhando para A e B - pelo menos, você também precisa observar a instalação / configuração de DI / convenções. Não é um problema para o programador (é exatamente o que eles querem, na verdade), mas um problema em potencial para um mantenedor / depurador (possivelmente o mesmo programador, posteriormente). Essa é uma troca que você faz, mesmo que sua estrutura de DI seja "perfeita".
Jeroen Mostert
88

A injeção de dependência é, como a maioria dos padrões, uma solução para os problemas . Então comece perguntando se você tem o problema em primeiro lugar. Caso contrário, o uso do padrão provavelmente tornará o código pior .

Considere primeiro se você pode reduzir ou eliminar dependências. Sendo todas as outras coisas iguais, queremos que cada componente de um sistema tenha o menor número possível de dependências. E se as dependências se foram, a questão de injetar ou não se torna discutível!

Considere um módulo que baixa alguns dados de um serviço externo, analisa e executa algumas análises complexas e grava para resultar em um arquivo.

Agora, se a dependência do serviço externo for codificada, será realmente difícil testar o processamento interno deste módulo. Portanto, você pode decidir injetar o serviço externo e o sistema de arquivos como dependências da interface, o que permitirá injetar zombarias em vez disso, o que, por sua vez, torna possível o teste de unidade da lógica interna.

Mas uma solução muito melhor é simplesmente separar a análise da entrada / saída. Se a análise for extraída para um módulo sem efeitos colaterais, será muito mais fácil testar. Observe que a simulação é um cheiro de código - nem sempre é evitável, mas, em geral, é melhor se você puder testar sem depender da simulação. Portanto, ao eliminar as dependências, você evita os problemas que o DI deve aliviar. Observe que esse design também adere muito melhor ao SRP.

Quero enfatizar que o DI não necessariamente facilita o SRP ou outros bons princípios de design, como separação de preocupações, alta coesão / baixo acoplamento e assim por diante. Pode muito bem ter o efeito oposto. Considere uma classe A que usa outra classe B internamente. B é usado apenas por A e, portanto, totalmente encapsulado e pode ser considerado um detalhe de implementação. Se você alterar isso para injetar B no construtor de A, expôs esse detalhe da implementação e agora tem conhecimento sobre essa dependência e sobre como inicializar B, a vida útil de B e assim por diante devem existir em algum outro lugar do sistema separadamente de A. Então, você tem uma arquitetura geral pior com preocupações de vazamento.

Por outro lado, existem alguns casos em que o DI é realmente útil. Por exemplo, para serviços globais com efeitos colaterais como um criador de logs.

O problema é quando padrões e arquiteturas se tornam objetivos em si mesmos, e não ferramentas. Apenas perguntando "Devemos usar DI?" é como colocar a carroça diante do cavalo. Você deve perguntar: "Temos algum problema?" e "Qual é a melhor solução para esse problema?"

Uma parte de sua pergunta se resume a: "Devemos criar interfaces supérfluas para satisfazer as demandas do padrão?" Você provavelmente já percebe a resposta para isso - absolutamente não ! Qualquer pessoa que diga o contrário está tentando vender algo para você - provavelmente horas de consultoria caras. Uma interface só tem valor se representar uma abstração. Uma interface que apenas imita a superfície de uma única classe é chamada de "interface de cabeçalho" e esse é um antipadrão conhecido.

JacquesB
fonte
15
Eu não poderia concordar mais! Observe também que zombar das coisas significa que não estamos realmente testando as implementações reais. Se for Ausado Bna produção, mas somente tiver sido testado MockB, nossos testes não nos dizem se funcionará na produção. Quando componentes puros (sem efeito colateral) de um modelo de domínio estão injetando e zombando uns dos outros, o resultado é um enorme desperdício de tempo de todos, uma base de código inchada e frágil e baixa confiança no sistema resultante. Zombe dos limites do sistema, não entre partes arbitrárias do mesmo sistema.
Warbo 29/05
17
@CarlLeth Por que você acha que o DI torna o código "testável e sustentável" e o código sem menos? JacquesB está certo em relação aos efeitos colaterais que prejudicam a testabilidade / manutenção. Se o código não tiver efeitos colaterais, não nos importamos com o que / onde / quando / como chama outro código; podemos mantê-lo simples e direto. Se o código tem efeitos colaterais, precisamos nos preocupar. O DI pode extrair efeitos colaterais das funções e colocá-lo em parâmetros, tornando essas funções mais testáveis, mas o programa mais complexo. Às vezes isso é inevitável (por exemplo, acesso ao banco de dados). Se o código não tiver efeitos colaterais, o DI é apenas uma complexidade inútil.
Warbo 29/05
13
@CarlLeth: DI é uma solução para o problema de tornar o código testável isoladamente se tiver dependências que o proíbam. Mas isso não reduz a complexidade geral, nem torna o código mais legível, o que significa que não aumenta necessariamente a capacidade de manutenção. No entanto, se todas essas dependências puderem ser eliminadas por uma melhor separação de preocupações, isso "anula" perfeitamente os benefícios da DI, porque anula a necessidade de DI. Geralmente, essa é uma solução melhor para tornar o código mais testável e sustentável ao mesmo tempo.
Doc Brown
5
@ Warbo Esse era o original e ainda provavelmente o único uso válido de zombaria. Mesmo nos limites do sistema, isso raramente é necessário. As pessoas realmente perdem muito tempo criando e atualizando testes quase inúteis.
Frank Hileman
6
@CarlLeth: ok, agora vejo de onde vem o mal-entendido. Você está falando sobre inversão de dependência . Mas, a pergunta, esta resposta e meus comentários são sobre DI = injeção de dependência .
Doc Brown
36

Na minha experiência, há uma série de desvantagens na injeção de dependência.

Primeiro, o uso do DI não simplifica os testes automatizados tanto quanto os anunciados. O teste de unidade de uma classe com uma implementação simulada de uma interface permite validar como essa classe irá interagir com a interface. Ou seja, permite que você teste de unidade como a classe em teste usa o contrato fornecido pela interface. No entanto, isso oferece uma garantia muito maior de que a entrada da classe em teste na interface é a esperada. Ele fornece uma garantia bastante fraca de que a classe em teste responde conforme o esperado para a saída da interface, pois é uma saída quase universalmente simulada, que está sujeita a bugs, simplificações excessivas e assim por diante. Em resumo, NÃO permite validar que a classe se comportará conforme o esperado com uma implementação real da interface.

Segundo, o DI torna muito mais difícil navegar pelo código. Ao tentar navegar para a definição de classes usadas como entrada para funções, uma interface pode ser qualquer coisa, desde um pequeno aborrecimento (por exemplo, onde existe uma única implementação) a um grande tempo gasto (por exemplo, quando uma interface excessivamente genérica como IDisposable é usada) ao tentar encontrar a implementação real sendo usada. Isso pode transformar um exercício simples como "Preciso corrigir uma exceção de referência nula no código que ocorre logo após a impressão dessa instrução de log" em um esforço de um dia.

Terceiro, o uso de DI e estruturas é uma faca de dois gumes. Pode reduzir bastante a quantidade de código da placa da caldeira necessária para operações comuns. No entanto, isso custa às custas de conhecimento detalhado da estrutura de DI específica para entender como essas operações comuns são realmente conectadas. Compreender como as dependências são carregadas na estrutura e adicionar uma nova dependência na estrutura para injetar pode exigir a leitura de uma quantidade razoável de material de plano de fundo e a seguir alguns tutoriais básicos sobre a estrutura. Isso pode transformar algumas tarefas simples em tarefas bastante demoradas.

Eric
fonte
Eu também acrescentaria que quanto mais você injetar, mais longos serão os tempos de inicialização. A maioria das estruturas de DI cria todas as instâncias singleton injetáveis ​​no momento da inicialização, independentemente de onde elas são usadas.
Rodney P. Barbati
7
Se você deseja testar uma classe com implementação real (não é uma farsa), você pode escrever testes funcionais - testes semelhantes aos testes de unidade, mas sem usar zombarias.
3030
2
Eu acho que seu segundo parágrafo precisa ser mais matizado: o DI por si só não torna mais difícil navegar pelo código. Na sua forma mais simples, o DI é simplesmente uma consequência de seguir o SOLID. O que aumenta a complexidade é o uso de indiretas desnecessárias e estruturas de DI . Fora isso, essa resposta atinge a unha na cabeça.
Konrad Rudolph
4
A injeção de dependência, fora dos casos em que é realmente necessário, também é um sinal de alerta de que outro código supérfluo pode estar presente em grande abundância. Os executivos geralmente ficam surpresos ao saber que os desenvolvedores acrescentam complexidade por uma questão de complexidade.
Frank Hileman
1
+1. Esta é a resposta real à pergunta que foi feita e deve ser a resposta aceita.
Mason Wheeler
13

Eu segui o conselho de Mark Seemann em "Injeção de dependência no .NET" - para supor.

O DI deve ser usado quando você tem uma 'dependência volátil', por exemplo, há uma chance razoável de que isso mude.

Portanto, se você acha que pode ter mais de uma implementação no futuro ou a implementação pode mudar, use o DI. Caso contrário, newestá bem.

Roubar
fonte
5
Observe que ele também oferece conselhos diferentes para OO e idiomas funcionais blog.ploeh.dk/2017/01/27/…
jk.
1
Esse é um bom argumento. Se criarmos interfaces para todas as dependências por padrão, é de alguma forma contra o YAGNI.
Landeeyo
4
Você pode fornecer uma referência para "Injeção de dependência no .NET" ?
Peter Mortensen
1
Se você estiver realizando testes de unidade, é altamente provável que sua dependência seja volátil.
Jaquez 30/05
11
O bom é que os desenvolvedores sempre são capazes de prever o futuro com perfeita precisão.
Frank Hileman
5

Minha maior preocupação sobre DI já foi mencionada em algumas respostas de uma maneira passageira, mas vou expandir um pouco aqui. DI (como é feito principalmente hoje, com contêineres etc.) realmente prejudica a legibilidade do código. E a legibilidade do código é sem dúvida a razão por trás da maioria das inovações de programação atuais. Como alguém disse - escrever código é fácil. Ler código é difícil. Mas também é extremamente importante, a menos que você esteja escrevendo algum tipo de pequeno utilitário descartável de gravação única.

O problema com o DI nesse sentido é que é opaco. O contêiner é uma caixa preta. Os objetos simplesmente aparecem de algum lugar e você não tem idéia - quem os construiu e quando? O que foi passado para o construtor? Com quem estou compartilhando esta instância? Quem sabe...

E quando você trabalha principalmente com interfaces, todos os recursos de "ir para a definição" do seu IDE ficam em chamas. É muito difícil rastrear o fluxo do programa sem executá-lo e apenas avançar para ver apenas qual implementação da interface foi usada neste local específico. E, ocasionalmente, há algum obstáculo técnico que impede você de avançar. E mesmo que você possa, se isso envolve atravessar as entranhas retorcidas do contêiner de DI, todo o caso rapidamente se torna um exercício de frustração.

Para trabalhar eficientemente com um pedaço de código que usou DI, você deve estar familiarizado com ele e já saber o que vai aonde.

Vilx-
fonte
3

Ativando implementações de comutação rapidamente (DbLogger em vez de ConsoleLogger, por exemplo)

Embora o DI em geral seja certamente uma coisa boa, sugiro não usá-lo cegamente para tudo. Por exemplo, nunca injeto registradores. Uma das vantagens do DI é tornar as dependências explícitas e claras. Não faz sentido listar ILoggercomo dependência de quase todas as classes - é apenas desorganização. É de responsabilidade do registrador fornecer a flexibilidade necessária. Todos os meus registradores são membros finais estáticos, posso considerar injetar um registrador quando precisar de um não estático.

Maior número de classes

Essa é uma desvantagem da estrutura de DI fornecida ou da estrutura de zombaria, não da própria DI. Na maioria dos lugares, minhas aulas dependem de aulas concretas, o que significa que não há necessidade de clichê. O Guice (uma estrutura Java DI) vincula, por padrão, uma classe a si próprio e eu só preciso substituir a ligação nos testes (ou conectá-los manualmente).

Criação de interfaces desnecessárias

Eu só crio as interfaces quando necessário (o que é bastante raro). Isso significa que, às vezes, tenho que substituir todas as ocorrências de uma classe por uma interface, mas o IDE pode fazer isso por mim.

Devemos usar injeção de dependência quando temos apenas uma implementação?

Sim, mas evite qualquer clichê adicional .

Deveríamos proibir a criação de novos objetos, exceto os de linguagem / estrutura?

Não. Haverá muitas classes de valor (imutável) e de dados (mutável), em que as instâncias são criadas e transmitidas e onde não há sentido em injetá-las - pois elas nunca são armazenadas em outro objeto (ou apenas em outros objetos). tais objetos).

Para eles, talvez seja necessário injetar uma fábrica, mas na maioria das vezes não faz sentido (imagine, por exemplo @Value class NamedUrl {private final String name; private final URL url;}; você realmente não precisa de uma fábrica aqui e não há nada a injetar).

Injetar uma única implementação é uma péssima idéia (digamos que temos apenas uma implementação para não querermos criar uma interface "vazia") se não planejamos testar uma unidade em particular em uma classe específica?

IMHO está bem, desde que não cause inchaço no código. Injete a dependência, mas não crie a interface (e nenhum XML de configuração maluco!), Pois você pode fazê-lo mais tarde, sem qualquer aborrecimento.

Na verdade, no meu projeto atual, existem quatro classes (entre centenas), que decidi excluir do DI , pois são classes simples usadas em muitos lugares, incluindo objetos de dados.


Outra desvantagem da maioria das estruturas de DI é a sobrecarga do tempo de execução. Isso pode ser movido para o tempo de compilação (para Java, há Dagger , nenhuma idéia sobre outras linguagens).

Outra desvantagem é a mágica que está acontecendo em todos os lugares, que pode ser diminuída (por exemplo, desativei a criação de proxy ao usar o Guice).

maaartinus
fonte
-4

Devo dizer que, na minha opinião, toda a noção de injeção de dependência é superestimada.

DI é o equivalente moderno dos valores globais. As coisas que você está injetando são singletons globais e objetos de código puro; caso contrário, você não pode injetá-los. A maioria dos usos de DI são forçados a você para usar uma determinada biblioteca (JPA, Spring Data, etc). Na maioria das vezes, o DI fornece o ambiente perfeito para nutrir e cultivar espaguete.

Com toda a honestidade, a maneira mais fácil de testar uma classe é garantir que todas as dependências sejam criadas em um método que possa ser substituído. Em seguida, crie uma classe de teste derivada da classe real e substitua esse método.

Então você instancia a classe Test e testa todos os seus métodos. Isso não ficará claro para alguns de vocês - os métodos que você está testando são os da classe em teste. E todos esses testes de método ocorrem em um único arquivo de classe - a classe de teste de unidade associada à classe em teste. Não há custos indiretos aqui - é assim que o teste de unidade funciona.

No código, esse conceito se parece com isso ...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

O resultado é exatamente equivalente ao uso do DI - ou seja, o ClassUnderTest está configurado para teste.

As únicas diferenças são que esse código é totalmente conciso, completamente encapsulado, mais fácil de codificar, mais fácil de entender, mais rápido, usa menos memória, não requer uma configuração alternativa, não requer estruturas, nunca será a causa de uma página 4 (WTF!) Rastreio de pilha que inclui exatamente as classes ZERO (0) que você escreveu e é completamente óbvio para qualquer pessoa com o menor conhecimento de OO, do iniciante ao Guru (você pensaria, mas estaria enganado).

Dito isto, é claro que não podemos usá-lo - é muito óbvio e não está na moda o suficiente.

No final do dia, porém, minha maior preocupação com o DI é a dos projetos que vi falhar miseravelmente, todos eles têm bases de código maciças onde o DI era a cola que mantinha tudo unido. O DI não é uma arquitetura - é realmente relevante apenas em algumas situações, a maioria das quais são impostas a você para usar outra biblioteca (JPA, Spring Data, etc.). Na maioria das vezes, em uma base de código bem projetada, a maioria dos usos de DI ocorreria em um nível abaixo do local em que suas atividades diárias de desenvolvimento acontecem.

Rodney P. Barbati
fonte
6
Você descreveu não o equivalente, mas o oposto da injeção de dependência. No seu modelo, todo objeto precisa conhecer a implementação concreta de todas as suas dependências, mas usar o DI que se torna responsabilidade do componente "principal" - para colar as implementações apropriadas. Lembre-se de que o DI acompanha a outra inversão de dependência de DI, onde você não deseja que os componentes de alto nível tenham dependências rígidas dos componentes de baixo nível.
Logan Pickup
1
é legal quando você tem apenas um nível de herança de classe e um nível de dependências. Certamente ele se transformará em inferno na terra à medida que se expandir?
Ewan
4
se você usar interfaces e mover initializeDependencies () para o construtor, é o mesmo, mas mais limpo. O próximo passo, adicionar parâmetros de construção significa que você pode acabar com todas as suas TestClasses.
Ewan
5
Há muita coisa errada nisso. Como outros já disseram, seu exemplo de 'DI equivalente' não é injeção de dependência, é a antítese e demonstra uma completa falta de entendimento do conceito e também apresenta outras armadilhas potenciais: objetos parcialmente inicializados são um cheiro de código Como Ewan sugere, mova a inicialização para o construtor e passe-os pelos parâmetros do construtor. Então você tem DI ...
Mr.Mindor
3
Adicionando ao @ Mr.Mindor: há um "acoplamento seqüencial" antipadrão mais geral que não se aplica apenas à inicialização. Se os métodos de um objeto (ou, geralmente, as chamadas de uma API) devem ser executados em uma ordem específica, por exemplo, barsó podem ser chamados depois foo, é uma API ruim. Ele está reivindicando fornecer a funcionalidade ( bar), mas não podemos realmente usá-la (pois foopode não ter sido chamada). Se você deseja manter seu initializeDependenciespadrão (anti?), Deve pelo menos torná-lo privado / protegido e chamá-lo automaticamente do construtor, para que a API seja sincera.
Warbo
-6

Realmente sua pergunta se resume a "O teste de unidade é ruim?"

99% de suas classes alternativas para injetar serão simulações que permitem o teste de unidade.

Se você realizar testes de unidade sem DI, terá o problema de como fazer com que as classes usem dados simulados ou um serviço simulado. Vamos dizer "parte da lógica", pois você pode não estar separando-a em serviços.

Existem maneiras alternativas de fazer isso, mas o DI é bom e flexível. Depois de testá-lo, você é praticamente forçado a usá-lo em qualquer lugar, pois você precisa de algum outro código, mesmo o chamado 'DI do pobre homem' que instancia os tipos concretos.

É difícil imaginar uma desvantagem tão ruim que a vantagem do teste de unidade seja superada.

Ewan
fonte
13
Eu discordo de sua afirmação de que DI é sobre teste de unidade. Facilitar o teste de unidade é apenas um dos benefícios da DI e, sem dúvida, o mais importante.
Robert Harvey
5
Não concordo com sua premissa de que o teste de unidade e o DI estão tão próximos. Ao usar um mock / stub, fazemos com que a suíte de testes fique um pouco mais: o sistema em teste se distancia do sistema real. Isso é objetivamente ruim. Às vezes, isso é compensado por uma vantagem: as chamadas FS falsificadas não exigem limpeza; solicitações de HTTP simuladas são rápidas, determinísticas e funcionam offline; Por outro lado, toda vez que usamos um código embutido newem um método, sabemos que o mesmo código em execução na produção estava sendo executado durante os testes.
Warbo 29/05
8
Não, não é “o teste de unidade é ruim?”, “Está zombando (a) é realmente necessário e (b) vale o aumento da complexidade incorrida?” Essa é uma pergunta muito diferente. O teste de unidade não é ruim (literalmente, ninguém está discutindo isso), e definitivamente vale a pena em geral. Mas nem todos os testes de unidade exigem zombaria, e a zombaria tem um custo substancial, portanto deve, pelo menos, ser usada criteriosamente.
Konrad Rudolph
6
@ Ewan Após o seu último comentário, acho que não concordamos. Estou dizendo que a maioria dos testes de unidade não precisa de DI [frameworks] , porque a maioria dos testes de unidade não precisa de zombaria. Na verdade, eu usaria isso como uma heurística para a qualidade do código: se a maior parte do seu código não puder ser testada em unidade sem objetos DI / mock, você terá escrito um código incorreto que foi acoplado com muita razão. A maioria dos códigos deve ser altamente dissociada, de responsabilidade única e de uso geral e ser trivialmente testável isoladamente.
Konrad Rudolph
5
@Ewan Seu link fornece uma boa definição de teste de unidade. Por essa definição, meu Orderexemplo é um teste de unidade: está testando um método (o totalmétodo de Order). Você está reclamando que está chamando código de 2 classes, minha resposta é e daí ? Não estamos testando "2 classes de uma vez", estamos testando o totalmétodo. Não devemos nos importar como um método faz seu trabalho: esses são detalhes de implementação; testá-los causa fragilidade, acoplamento rígido, etc. Apenas nos preocupamos com o comportamento do método (valor de retorno e efeitos colaterais), não com as classes / módulos / registradores de CPU / etc. usado no processo.
Warbo