Quais são as desvantagens de usar a injeção de dependência? [fechadas]

336

Estou tentando introduzir a DI como um padrão aqui no trabalho e um de nossos principais desenvolvedores gostaria de saber: Quais são as desvantagens de usar o padrão de injeção de dependência, se houver alguma ?

Note que estou procurando aqui uma lista - se possível - exaustiva, não uma discussão subjetiva sobre o tópico.


Esclarecimento : estou falando do padrão de Injeção de Dependência (consulte este artigo de Martin Fowler), não de uma estrutura específica, seja baseada em XML (como Spring) ou baseada em código (como Guice) ou "autolaminada" .


Edit : Algumas grandes discussões adicionais / discussões / debates acontecendo / r / programação aqui.

Epaga
fonte
Pode ser uma boa ideia especificar se devemos discutir o próprio DI ou um tipo específico de ferramenta que o suporte (baseado em XML ou não).
precisa
5
a diferença é que NÃO estou procurando respostas que se apliquem apenas a estruturas específicas, como "XML é uma merda". :) Estou procurando respostas que se apliquem ao conceito de DI, conforme descrito por Fowler aqui: martinfowler.com/articles/injection.html
Epaga #
3
Não vejo desvantagens de DI em geral (construtor, setter ou método). Ele se desacopla e simplifica sem sobrecarga.
Kissaki
2
@kissaki bem além do setter injeta coisas. Melhor fazer objetos que sejam utilizáveis ​​na construção. Se você não acredita em mim, tente adicionar outro colaborador a um objeto com injeção 'setter', você vai ter um NPE com certeza ....
time4tea

Respostas:

209

Alguns pontos:

  • A DI aumenta a complexidade, geralmente aumentando o número de classes, pois as responsabilidades são mais separadas, o que nem sempre é benéfico
  • Seu código será (um pouco) acoplado à estrutura de injeção de dependência que você usa (ou mais geralmente como você decide implementar o padrão DI)
  • Contêineres DI ou abordagens que executam a resolução de tipos geralmente incorrem em uma pequena penalidade de tempo de execução (muito insignificante, mas existe)

Geralmente, o benefício da dissociação torna cada tarefa mais simples de ler e entender, mas aumenta a complexidade de orquestrar as tarefas mais complexas.

Håvard S
fonte
72
- A separação de classes reduz a complexidade. Muitas classes não tornam um aplicativo complexo. - Você deve ter apenas uma dependência da estrutura de DI na raiz do aplicativo.
Robert
91
Nós somos humanos; nossa memória de curto prazo é limitada e não pode lidar com muitos <xxx> simultaneamente. É o mesmo para as classes e para os métodos, funções, arquivos ou qualquer construção que você usa para desenvolver seu programa.
Håvard S
45
@ Havard S: Sim, mas 5 classes realmente complexas não são mais simples do que 15 classes simples. A maneira de diminuir a complexidade é reduzir o conjunto de trabalho exigido pelo programador que trabalha no código - classes complexas e altamente interdependentes não conseguem isso.
Kyoryu 9/03/10
35
@kyoryu Acho que todos concordamos com isso. Lembre-se de que complexidade não é a mesma coisa que acoplamento . A diminuição do acoplamento pode aumentar a complexidade. Eu realmente não estou dizendo que o DI é uma coisa ruim, porque aumenta o número de classes, destacando as possíveis desvantagens associadas a ele. :)
Håvard S
96
+1 para "DI aumenta a complexidade" Isso é verdade em DI e além. (Quase) sempre que aumentamos a flexibilidade, aumentamos a complexidade. É uma questão de equilíbrio, que começa com o conhecimento das vantagens e desvantagens. Quando as pessoas dizem 'não há desvantagens', é um indicador claro de que ainda não entenderam completamente a coisa.
Don Branson
183

O mesmo problema básico que você costuma enfrentar com programação orientada a objetos, regras de estilo e praticamente tudo o mais. É possível - muito comum, de fato - fazer muita abstração e acrescentar muita indireção, e geralmente aplicar boas técnicas excessivamente e nos lugares errados.

Todo padrão ou outra construção que você aplica traz complexidade. A abstração e o indireto espalham informações, às vezes afastando detalhes irrelevantes, mas igualmente dificultando a compreensão do que está acontecendo. Toda regra que você aplica traz inflexibilidade, descartando opções que podem ser apenas a melhor abordagem.

O objetivo é escrever um código que faça o trabalho e seja robusto, legível e sustentável. Você é um desenvolvedor de software - não um construtor de torres de marfim.

Links relevantes

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Provavelmente, a forma mais simples de injeção de dependência (não ria) é um parâmetro. O código dependente depende dos dados e esses dados são injetados através da passagem do parâmetro.

Sim, é bobagem e não aborda o ponto de injeção de dependência orientado a objetos, mas um programador funcional lhe dirá que (se você tiver funções de primeira classe) esse é o único tipo de injeção de dependência que você precisa. O ponto aqui é pegar um exemplo trivial e mostrar os possíveis problemas.

Vamos pegar essa função tradicional simples - a sintaxe C ++ não é significativa aqui, mas eu tenho que escrevê-la de alguma forma ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Eu tenho uma dependência que quero extrair e injetar - o texto "Hello World". Bastante fácil...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

Como isso é mais inflexível que o original? Bem, e se eu decidir que a saída deve ser unicode. Eu provavelmente quero mudar de std :: cout para std :: wcout. Mas isso significa que minhas cordas precisam ser de wchar_t, não de char. Todo chamador deve ser alterado ou (mais razoavelmente), a implementação antiga é substituída por um adaptador que traduz a cadeia e chama a nova implementação.

Esse é o trabalho de manutenção que não seria necessário se mantivéssemos o original.

E se parecer trivial, dê uma olhada nesta função do mundo real a partir da API do Win32 ...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

São 12 "dependências" para lidar. Por exemplo, se as resoluções de tela ficarem realmente grandes, talvez precisemos de valores de coordenadas de 64 bits - e outra versão do CreateWindowEx. E sim, já existe uma versão mais antiga, que provavelmente é mapeada para a versão mais recente nos bastidores ...

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Essas "dependências" não são apenas um problema para o desenvolvedor original - todos que usam essa interface precisam procurar quais são as dependências, como são especificadas e o que elas significam e descobrir o que fazer para o aplicativo. É aqui que as palavras "padrões sensíveis" podem tornar a vida muito mais simples.

A injeção de dependência orientada a objetos não é diferente em princípio. Escrever uma classe é uma sobrecarga, tanto no texto do código-fonte quanto no tempo do desenvolvedor, e se essa classe for escrita para fornecer dependências de acordo com algumas especificações de objetos dependentes, o objeto dependente será bloqueado no suporte a essa interface, mesmo que seja necessário para substituir a implementação desse objeto.

Nada disso deve ser lido como alegando que a injeção de dependência é ruim - longe disso. Mas qualquer boa técnica pode ser aplicada excessivamente e no lugar errado. Assim como nem toda cadeia precisa ser extraída e transformada em parâmetro, nem todo comportamento de baixo nível precisa ser extraído de objetos de alto nível e transformado em uma dependência injetável.

Steve314
fonte
3
A injeção de dependência não possui nenhuma dessas desvantagens. Isso muda apenas a maneira como você passa as dependências para os objetos, o que não adiciona complexidade nem inflexibilidade. Pelo contrário.
Kissaki
24
@ Kissaki - sim, isso muda a maneira como você passa suas dependências. E você precisa escrever um código para lidar com essa passagem de dependências. E antes de fazer isso, você precisa descobrir quais dependências passar, defini-las de uma maneira que faça sentido para alguém que não codificou o código dependente etc. Você pode evitar o impacto no tempo de execução (por exemplo, usando parâmetros de política para modelos em C ++), mas ainda é o código que você precisa escrever e manter. Se for justificado, é um preço muito pequeno para uma grande recompensa, mas se você fingir que não há preço a pagar, isso significa que você estará pagando esse preço quando não houver ganho.
Steve314
6
@Kissaki - quanto à inflexibilidade, depois de especificar quais são suas dependências, você fica preso a isso - outras pessoas escrevem dependências para injetar de acordo com essa especificação. Se você precisar de uma nova implementação para o código dependente, que precisa de dependências um pouco diferentes - difícil, você está travado. É hora de começar a escrever alguns adaptadores para essas dependências (e um pouco mais de sobrecarga e outra camada de abstração).
101311 Steve314
Não sei se entendi esse exemplo, se você considerar sobrecarregar. Para gerar o literal "Hello World", usamos a assinatura (). Para gerar uma string de 8 bits, use a assinatura (char). Para gerar unicode, a sobrecarga (wchar_t). Obviamente, nesse caso, (wchar_t) é o que os outros chamam nos bastidores. Não é necessária muita reescrita de código lá. Estou esquecendo de algo?
ingredient_15939
2
@ ingrediente_15939 - sim, este é um exemplo de brinquedo simplificado. Se você estava realmente fazendo isso, basta usar sobrecarga em vez de um adaptador, pois o custo de duplicar o código é menor que o custo do adaptador. Mas observe que a duplicação de código geralmente é uma coisa ruim, e o uso de adaptadores para evitar a duplicação de código é outra boa técnica (que às vezes pode ser usada demais e nos lugares errados).
Steve314
77

Aqui está minha própria reação inicial: basicamente as mesmas desvantagens de qualquer padrão.

  • leva tempo para aprender
  • se incompreendido, pode levar a mais mal do que bem
  • se levado ao extremo, pode ser mais trabalhoso do que justificaria o benefício
Epaga
fonte
2
Por que leva tempo para aprender? Eu pensei que isso simplifica as coisas em comparação com o ejb.
fastcodejava
8
Isso parece ondulado, considerando que você não quer uma discussão subjetiva. Sugiro construir (coletivamente, se necessário) um exemplo realista para exame. Construir uma amostra sem DI seria um bom ponto de partida. Em seguida, poderíamos desafiá-lo às mudanças de requisitos e examinar o impacto. (Isto é extremamente conveniente para mim para sugerir, pelo caminho, como eu estou a ponto de ir para a cama.)
Jesse Millikan
4
Isso realmente precisa de alguns exemplos para apoiá-lo, e tendo ensinado dezenas de pessoas a DI, posso dizer que é realmente um conceito muito simples de aprender e começar a colocar em prática.
Chillitom
4
Eu realmente não considero DI um padrão. Ele (juntamente com a IoC) é realmente mais um modelo de programação - se você os seguir completamente, seu código parecerá muito mais com o ator do que com o OO pseudo-processual "típico".
Kyoryu
11
+1 Estou usando o Symfony 2, o DI está em todo o código do usuário, está esgotando apenas o uso.
MGP 22/02
45

A maior "desvantagem" da Inversão de Controle (não exatamente a DI, mas próxima o suficiente) é que ela tende a remover um único ponto para examinar uma visão geral de um algoritmo. Isso é basicamente o que acontece quando você dissocia o código - a capacidade de procurar em um só lugar é um artefato de acoplamento rígido.

kyoryu
fonte
2
Mas certamente essa "desvantagem" é por causa da natureza do problema que estamos resolvendo, dissociando-o para que possamos alterar facilmente a implementação, significa que não há um lugar para se olhar, e que relevância é poder vê-lo? O único caso que posso pensar é na depuração e o ambiente de depuração deve poder entrar na implementação.
vickirk
3
É por isso que a palavra "desvantagem" estava entre aspas :) O acoplamento solto e o encapsulamento forte impedem "um lugar para ver tudo", tipo por definição. Veja meus comentários sobre a resposta da Havard S, se você sentir que sou contra o DI / IoC.
Kyoryu 12/03/10
11
Eu concordo (eu até votei em você). Eu estava apenas fazendo o ponto.
vickirk
11
@vickirk: O lado ruim, eu acho, é que é difícil para um humano entender. A dissociação das coisas aumenta a complexidade no sentido de que é mais difícil para os seres humanos entenderem e levarem mais tempo para serem compreendidos completamente.
Bjarke Freund-Hansen
2
Pelo contrário, uma vantagem do DI é que você pode olhar para o construtor de uma classe individual e descobrir instantaneamente de que classe depende (ou seja, de quais classes precisam fazer seu trabalho). Isso é muito mais difícil para outro código, pois o código pode criar classes à vontade.
Contango 26/09/13
42

Eu não acho que essa lista exista, no entanto, tente ler esses artigos:

Gabriel Ščerbák
fonte
7
Estou curioso, quando alguém pergunta por vantagens, por que as respostas em espírito "não existem" são votadas e aquelas que contêm alguma informação relacionada à pergunta não?
Gabriel Ščerbák
12
-1 porque não estou procurando um conjunto de links, estou procurando respostas reais: que tal resumir os pontos de cada artigo? Então eu votaria em seu lugar. Observe que os artigos em si são bastante interessantes, embora pareça que os dois primeiros são realmente negativos de DI?
Epaga
4
Eu me pergunto se a resposta mais votada também foi rebaixada, porque realmente não responde à pergunta: :) Claro, eu sei o que você perguntou e o que eu respondi, não estou pedindo votos, apenas estou confusa por que meu A resposta não está ajudando, ao mesmo tempo em que aparentemente diz que a desvantagem do DI é que é muito legal está ajudando muito.
Gabriel Ščerbák
41

Eu tenho usado o Guice (framework Java DI) extensivamente nos últimos 6 meses. Embora, no geral, eu acho ótimo (especialmente do ponto de vista de teste), existem algumas desvantagens. Mais notavelmente:

  • Código pode se tornar mais difícil de entender. A injeção de dependência pode ser usada de maneiras muito ... criativas .... Por exemplo, encontrei um código que usava uma anotação personalizada para injetar um determinado IOStreams (por exemplo: @ Server1Stream, @ Server2Stream). Embora isso funcione, e admito que tenha uma certa elegância, torna a compreensão das injeções do Guice um pré-requisito para a compreensão do código.
  • Maior curva de aprendizado ao aprender projeto. Isso está relacionado ao ponto 1. Para entender como um projeto que usa injeção de dependência funciona, você precisa entender o padrão de injeção de dependência e a estrutura específica. Quando comecei no meu trabalho atual, passei algumas horas confusas reclamando o que Guice estava fazendo nos bastidores.
  • Os construtores se tornam grandes. Embora isso possa ser amplamente resolvido com um construtor padrão ou uma fábrica.
  • Erros podem ser ofuscados. Meu exemplo mais recente disso foi uma colisão com dois nomes de bandeira. Guice engoliu o erro silenciosamente e uma das minhas bandeiras não foi inicializada.
  • Os erros são enviados para o tempo de execução. Se você configurar seu módulo Guice incorretamente (referência circular, ligação incorreta, ...) a maioria dos erros não será descoberta durante o tempo de compilação. Em vez disso, os erros são expostos quando o programa é realmente executado.

Agora que eu reclamei. Deixe-me dizer que continuarei (de bom grado) a usar o Guice no meu projeto atual e provavelmente no meu próximo. A injeção de dependência é um padrão excelente e incrivelmente poderoso. Mas isso definitivamente pode ser confuso e você quase certamente passará algum tempo xingando qualquer estrutura de injeção de dependência que escolher.

Além disso, concordo com outros pôsteres de que a injeção de dependência pode ser usada em excesso.

Andy Peck
fonte
14
Eu não entendo por que as pessoas não fazem uma grande quantidade de "erros são empurrados para o tempo de execução" para mim, isso é um rompimento de acordos, erros de digitação estática e tempo de compilação são o maior presente dado aos desenvolvedores, que eu não jogaria -los para qualquer coisa
Richard Tingle
2
@RichardTingle, IMO durante a inicialização do aplicativo Os módulos DI serão inicializados primeiro, para que qualquer configuração incorreta no módulo fique visível assim que o aplicativo for iniciado, e não após alguns dias ou horas. Também é possível carregar módulos de forma incremental, mas se mantivermos o espírito de DI, limitando o carregamento do módulo antes de inicializar a lógica do aplicativo, poderemos isolar com êxito as ligações incorretas no início do aplicativo. Mas se o configurarmos como anti-padrão do localizador de serviço, essas más ligações certamente serão uma surpresa.
K4vin
@RichardTingle Entendo que nunca será semelhante à rede de segurança fornecida pelo compilador, mas o DI como uma ferramenta usada corretamente, conforme indicado na caixa, esses erros de tempo de execução são limitados à inicialização do aplicativo. Em seguida, podemos ver a inicialização do aplicativo como uma espécie de fase de compilação para os módulos DI. Na minha experiência, na maioria das vezes, se o aplicativo for iniciado, não haverá ligações ruins ou referências incorretas nele. PS - Eu tenho usado NInject com c #
k4vin
@RichardTingle - Concordo com você, mas é uma troca para obter um código fracamente acoplado e, portanto, testável. E como o k4vin disse, as dependências ausentes são encontradas na inicialização e o uso de interfaces ainda ajuda com erros de tempo de compilação.
Andrei Epure 28/02
11
Eu adicionaria "Difícil manter o código limpo" em sua lista. Você nunca sabe se pode excluir um registro antes de testar extensivamente o código sem ele.
Fernacolo 21/09/19
24

Código sem qualquer ID corre o risco conhecido de se envolver no código Spaghetti - alguns sintomas são que as classes e métodos são muito grandes, fazem muito e não podem ser facilmente alterados, discriminados, refatorados ou testados.

Código com DI usado muito pode ser código Ravioli, onde cada classe pequena é como um nugget de ravioli individual - faz uma pequena coisa e o princípio de responsabilidade única é respeitado, o que é bom. Mas, olhando as aulas por conta própria, é difícil ver o que o sistema como um todo faz, pois isso depende de como todas essas pequenas partes se encaixam, o que é difícil de ver. Parece apenas uma grande pilha de coisas pequenas.

Ao evitar a complexidade de espaguete de grandes bits de código acoplado em uma classe grande, você corre o risco de outro tipo de complexidade, onde existem muitas classes pequenas simples e as interações entre elas são complexas.

Eu não acho que isso seja uma desvantagem fatal - o DI ainda vale muito a pena. Algum grau de estilo ravioli com turmas pequenas que fazem apenas uma coisa é provavelmente bom. Mesmo em excesso, não acho que seja ruim como código de espaguete. Mas estar ciente de que isso pode ser levado longe demais é o primeiro passo para evitá-lo. Siga os links para discussão de como evitá-lo.

Anthony
fonte
11
Sim, eu gosto do termo "código ravioli"; Exprimo-o mais longamente quando falo sobre a desvantagem do DI. Ainda assim, não consigo imaginar desenvolver nenhuma estrutura ou aplicativo real em Java sem DI.
Howard M. Lewis Ship
13

Se você tem uma solução doméstica, as dependências estão certas na sua cara no construtor. Ou talvez como parâmetros de método que, novamente, não sejam muito difíceis de detectar. Embora as dependências gerenciadas pela estrutura, se levadas ao extremo, possam começar a parecer mágicas.

No entanto, ter muitas dependências em muitas classes é um sinal claro de que sua estrutura de classes está errada. Portanto, de certa forma, a injeção de dependência (cultivada em casa ou gerenciada por estrutura) pode ajudar a trazer à tona questões gritantes de design que, de outra forma, poderiam estar escondidas no escuro.


Para ilustrar melhor o segundo ponto, aqui está um trecho deste artigo ( fonte original ) que, de todo o coração, acredito ser o problema fundamental na construção de qualquer sistema, não apenas de computadores.

Suponha que você queira criar um campus universitário. Você deve delegar parte do design aos estudantes e professores, caso contrário, o edifício da Física não funcionará bem para o pessoal da física. Nenhum arquiteto sabe o suficiente sobre o que as pessoas de física precisam fazer tudo sozinhas. Mas você não pode delegar o design de todos os cômodos aos seus ocupantes, porque então você terá uma pilha gigante de escombros.

Como você pode distribuir a responsabilidade pelo design em todos os níveis de uma grande hierarquia, mantendo a consistência e a harmonia do design geral? Esse é o problema de design arquitetônico que Alexander está tentando resolver, mas também é um problema fundamental do desenvolvimento de sistemas de computadores.

O DI resolve esse problema? Não . Mas ajuda a ver claramente se você está tentando delegar a responsabilidade de projetar todos os cômodos para seus ocupantes.

Anurag
fonte
13

Uma coisa que me faz me contorcer um pouco com DI é a suposição de que todos os objetos injetados são baratos para instanciar e não produzem efeitos colaterais OU - a dependência é usada com tanta freqüência que supera qualquer custo de instanciação associado.

Onde isso pode ser significativo é quando uma dependência não é freqüentemente usada em uma classe consumidora; como algo como um IExceptionLogHandlerService. Obviamente, um serviço como esse é chamado (espero :) :) raramente dentro da classe - presumivelmente apenas em exceções que precisam ser registradas; ainda o padrão canônico de injeção de construtor ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... exige que uma instância "ativa" deste serviço seja fornecida, danificando os custos / efeitos colaterais necessários para obtê-lo lá. Não é provável que sim, mas e se a construção dessa instância de dependência envolvesse uma ocorrência de serviço / banco de dados, ou pesquisas de arquivos de configuração ou bloqueasse um recurso até ser descartado? Se, em vez disso, esse serviço fosse construído conforme necessário, localizado no serviço ou gerado pela fábrica (todos com problemas próprios), você levaria o custo da construção somente quando necessário.

Agora, é um princípio de design de software geralmente aceito que a construção de um objeto é barata e não produz efeitos colaterais. E embora essa seja uma boa ideia, nem sempre é o caso. No entanto, o uso típico de injeção de construtor exige basicamente que esse seja o caso. Ou seja, ao criar uma implementação de uma dependência, é necessário projetá-la com o DI em mente. Talvez você tenha tornado a construção de objetos mais dispendiosa para obter benefícios em outros lugares, mas se essa implementação for injetada, provavelmente forçará você a reconsiderar esse design.

A propósito, certas técnicas podem atenuar esse problema exato, permitindo o carregamento lento de dependências injetadas, por exemplo, fornecendo uma classe a uma Lazy<IService>instância como a dependência. Isso mudaria os construtores de seus objetos dependentes e tornaria ainda mais conscientes dos detalhes da implementação, como as despesas de construção de objetos, o que também não é desejável.

ckittel
fonte
11
Entendo o que você está dizendo, mas não acho justo dizer "quando você cria uma implementação de uma dependência, precisa projetá-la com o DI em mente" - acho que uma declaração mais precisa seria "DI works melhor quando usado com classes implementadas com práticas recomendadas relacionadas ao custo da instanciação e à falta de efeitos colaterais ou modos de falha durante a construção ". Na pior das hipóteses, você sempre pode injetar uma implementação de proxy lenta que adie a alocação do objeto real até o primeiro uso.
Jolly Roger
11
Qualquer contêiner IoC moderno permitirá que você especifique a vida útil de um objeto para um tipo / interface abstrata específica (sempre exclusivo, singleton, com escopo http, etc.). Você também pode fornecer um método / delegado de fábrica para instanciar preguiçosamente usando Func <T> ou Lazy <T>.
Dmitry S.
Spot on. Instanciar dependências desnecessariamente custa memória. Eu normalmente utilizo o "carregamento lento" com getters que apenas instanciam uma classe quando solicitados. Você pode fornecer um construtor padrão sem parâmetros, bem como construtores subseqüentes que aceitam dependências instanciadas. No primeiro caso, uma classe que implementa o IErrorLogger, por exemplo, é instanciada apenas this.errorLogger.WriteError(ex)quando ocorre um erro em uma instrução try / catch.
John Bonfardeci 23/02
12

A ilusão de que você dissociou seu código apenas implementando a injeção de dependência sem realmente dissociá-lo. Eu acho que é a coisa mais perigosa do DI.

Joey Guerra
fonte
11

Este é mais um truque. Mas uma das desvantagens da injeção de dependência é que torna um pouco mais difícil para as ferramentas de desenvolvimento raciocinar e navegar no código.

Especificamente, se você pressionar Control-Click / Command-Click em uma chamada de método no código, levará você à declaração do método em uma interface em vez da implementação concreta.

Isso é realmente uma desvantagem do código fracamente acoplado (código projetado pela interface) e se aplica mesmo se você não usar injeção de dependência (ou seja, mesmo se simplesmente usar fábricas). Mas o advento da injeção de dependência é o que realmente incentivou o código pouco acoplado às massas, então pensei em mencioná-lo.

Além disso, os benefícios do código fracamente acoplado superam isso, então eu o chamo de nitpick. Embora eu tenha trabalhado o suficiente para saber que esse é o tipo de reação que você pode ter se tentar introduzir injeção de dependência.

Na verdade, eu arriscaria adivinhar que, para cada "desvantagem" que você pode encontrar para injeção de dependência, você encontrará muitas vantagens que superam isso.

Jack Leow
fonte
5
Ctrl-Shift-B com ReSharper leva você a implementação
adrianm
11
Mas, novamente que nós não (em um mundo ideal) ter pelo menos 2 implementações de tudo, ou seja, pelo menos uma implementação verdadeira e uma falsa para testes de unidade ;-)
vickirk
11
No Eclipse, se você pressionar Ctrl e passar o mouse sobre o código de chamada de método, ele mostrará um menu para abrir a Declaração ou Implementação.
Sam Hasler
Se você estiver na definição da interface, destaque o nome do método e pressione Ctrl + T para exibir a hierarquia de tipos para esse método - você verá todos os implementadores desse método em uma árvore de hierarquia de tipos.
Qualidafial
10

A injeção de dependência baseada em construtores (sem o auxílio de "estruturas" mágicas) é uma maneira limpa e benéfica de estruturar o código OO. Nas melhores bases de código que eu já vi, ao longo dos anos que passei com outros ex-colegas de Martin Fowler, comecei a perceber que a maioria das boas aulas escritas dessa maneira acaba tendo um único doSomethingmétodo.

A principal desvantagem, então, é que, uma vez que você percebe que é apenas uma maneira desordenada de escrever OO para classes, a fim de obter os benefícios da programação funcional, sua motivação para escrever código OO pode evaporar rapidamente.

sanityinc
fonte
11
O problema com o construtor é que você sempre precisará adicionar mais argumentos do construtor ...
Miguel Ping
11
Haha Se você fizer isso demais, em breve verá que seu código é ruim e você o refatorará. Além disso, ctrl-f6 não é tão difícil.
Time4tea
E, claro, o inverso também é verdadeiro: é tudo apenas uma kludgy longa-handed maneira funcional de escrever classes como fechamentos a fim de obter os benefícios da programação OO
CurtainDog
Muitos anos atrás, construí um sistema de objetos usando fechamentos em Perl, para que eu entenda o que você está dizendo, mas encapsular dados não é um benefício exclusivo da programação OO, portanto, não está claro quais são esses recursos benéficos do OO que seriam comparáveis. kludgy e mão longa para obter em uma linguagem funcional.
sanityinc
9

Acho que a injeção de construtores pode levar a grandes e feios construtores (e eu a uso em toda a minha base de código - talvez meus objetos sejam muito granulares?). Além disso, às vezes com injeção de construtor, acabo com dependências circulares horríveis (embora isso seja muito raro); portanto, você pode ter que ter algum tipo de ciclo de vida de estado pronto com várias rodadas de injeção de dependência em um sistema mais complexo.

No entanto, sou a favor da injeção de construtor em detrimento da injeção de setter porque, uma vez que meu objeto é construído, sei sem dúvida em que estado ele está, seja em um ambiente de teste de unidade ou carregado em algum contêiner do IOC. O que, de uma maneira indireta, está dizendo o que sinto é a principal desvantagem da injeção de incubadora.

(como nota de rodapé, acho o tópico todo bastante "religioso", mas sua milhagem varia com o nível de zelo técnico da sua equipe de desenvolvimento!)

James B
fonte
8
Se você tem grandes construtores feios, pode ser que suas classes sejam grandes e você só precisa de muitas dependências?
Robert
4
Essa é certamente uma possibilidade que estou disposta a entreter! ... Infelizmente, não tenho uma equipe com a qual possa fazer revisões por pares. violinos tocam suavemente no fundo, e então desaparece da tela para preto ...
James B
11
Como Mark Seeman triste acima "Pode ser perigoso para sua carreira" ...
Robert
Eu não tinha pistas de que as narrativas de fundo poderiam ser tão expressivas aqui: P
Anurag
@ Robert Estou tentando encontrar a citação, onde ele disse isso?
liang
8

Se você estiver usando o DI sem um contêiner de IOC, a maior desvantagem é ver rapidamente quantas dependências seu código realmente possui e quão fortemente está tudo acoplado. ("Mas achei que era um bom design!") A progressão natural é avançar para um contêiner do COI que pode levar um pouco de tempo para aprender e implementar (não tão ruim quanto a curva de aprendizado do WPF, mas não é gratuito) ou). A desvantagem final é que alguns desenvolvedores começarão a escrever testes de unidade honestos e bondosos e eles levarão tempo para descobrir isso. Os desenvolvedores que anteriormente poderiam acionar algo em meio dia repentinamente passarão dois dias tentando descobrir como zombar de todas as suas dependências.

Semelhante à resposta de Mark Seemann, o ponto principal é que você passa um tempo se tornando um desenvolvedor melhor, em vez de juntar partes do código e jogá-lo na porta / em produção. Qual seria sua empresa preferida? Só você pode responder isso.

Mike Post
fonte
Bom ponto. Isso é má qualidade / entrega rápida vs boa qualidade / entrega lenta. A desvantagem? DI não é mágica, esperando que seja a desvantagem.
Liang
5

A DI é uma técnica ou padrão e não está relacionada a nenhuma estrutura. Você pode conectar suas dependências manualmente. O DI ajuda você com SR (responsabilidade única) e SoC (separação de preocupações). O DI leva a um design melhor. Do meu ponto de vista e experiência, não há desvantagens . Como em qualquer outro padrão, você pode entendê-lo errado ou mal (mas o que é difícil no DI).

Se você introduzir o DI como princípio em um aplicativo herdado, usando uma estrutura - o maior erro que você poderá cometer é usá-lo como um Localizador de Serviço. O DI + Framework em si é ótimo e apenas melhorou as coisas em todos os lugares que eu vi! Do ponto de vista organizacional, existem problemas comuns a cada novo processo, técnica, padrão, ...:

  • Você tem que treinar sua equipe
  • Você precisa alterar seu aplicativo (que inclui riscos)

Em geral, você precisa investir tempo e dinheiro , além disso, não há desvantagens, realmente!

Robert
fonte
3

Legibilidade do código. Você não poderá descobrir facilmente o fluxo de código, pois as dependências estão ocultas nos arquivos XML.

Rahul
fonte
9
Você não precisa usar arquivos de configuração XML para injeção de dependência - escolha um contêiner de DI que suporte a configuração no código.
Håvard S
8
A injeção de dependência não implica necessariamente arquivos XML. Parece que ainda pode ser o caso em Java, mas no .NET abandonamos esse acoplamento anos atrás.
precisa
6
Ou não use um recipiente DI.
precisa
se você souber que o código está usando DI, você pode facilmente assumir quem define as dependências.
Bozho 9/03/10
Em Java, o Guice do Google não precisa de arquivos XML, por exemplo.
Epaga 14/02
2

Duas coisas:

  • Eles exigem suporte extra à ferramenta para verificar se a configuração é válida.

Por exemplo, o IntelliJ (edição comercial) tem suporte para verificar a validade de uma configuração do Spring e sinalizará erros como violações de tipo na configuração. Sem esse tipo de suporte de ferramenta, você não pode verificar se a configuração é válida antes de executar os testes.

Essa é uma das razões pelas quais o padrão 'bolo' (como é conhecido pela comunidade Scala) é uma boa idéia: a fiação entre os componentes pode ser verificada pelo verificador de tipos. Você não tem esse benefício com anotações ou XML.

  • Isso torna a análise estática global de programas muito difícil.

Estruturas como Spring ou Guice dificultam determinar estaticamente como será o gráfico de objetos criado pelo contêiner. Embora eles criem um gráfico de objeto quando o contêiner é iniciado, eles não fornecem APIs úteis que descrevem o gráfico de objeto que / seria / seria criado.

Martin Ellis
fonte
11
Este é um touro absoluto. Injeção de dependência é um conceito, não requer uma estrutura fragmentada. Tudo que você precisa é novo. Ditch spring e toda essa porcaria, então suas ferramentas funcionarão muito bem, e você poderá refatorar seu código muito mais.
Time4tea
Verdade, bom ponto. Eu deveria ter ficado mais claro que estava falando sobre problemas com estruturas de DI e não sobre o padrão. É possível que eu tenha perdido o esclarecimento da pergunta quando respondi (supondo que ela estivesse lá na época).
Martin Ellis
Ah Nesse caso, felizmente retiro meu aborrecimento. desculpas.
time4tea 14/02
2

Parece que os supostos benefícios de uma linguagem de tipo estatístico diminuem significativamente quando você está constantemente empregando técnicas para solucionar a digitação estática. Uma grande loja Java que acabei de entrevistar estava mapeando suas dependências de compilação com análise estática de código ... que precisava analisar todos os arquivos Spring para ser eficaz.

Chris D
fonte
1

Isso pode aumentar o tempo de inicialização do aplicativo, porque o contêiner de IoC deve resolver as dependências de maneira adequada e, às vezes, exige várias iterações.

romano
fonte
3
Apenas para dar uma figura, um contêiner de DI deve resolver várias dependências de milhares por segundo. ( codinginstinct.com/2008/05/… ) Os contêineres DI permitem instanciação diferida. O desempenho (mesmo em aplicativos grandes) não deve ser um problema. Ou pelo menos o problema de desempenho deve ser resolvido e não deve ser um motivo para decidir contra a IoC e suas estruturas.
Robert