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.
Respostas:
Alguns pontos:
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.
fonte
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 ...
Eu tenho uma dependência que quero extrair e injetar - o texto "Hello World". Bastante fácil...
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.
fonte
Aqui está minha própria reação inicial: basicamente as mesmas desvantagens de qualquer padrão.
fonte
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.
fonte
Eu não acho que essa lista exista, no entanto, tente ler esses artigos:
O DI pode obscurecer o código (se você não estiver trabalhando com um bom IDE)
O uso incorreto da IoC pode levar a códigos incorretos, de acordo com o tio Bob.
Precisa olhar para o excesso de engenharia e criar versatilidade desnecessária.
fonte
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:
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.
fonte
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.
fonte
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.
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.
fonte
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 ...... 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.fonte
this.errorLogger.WriteError(ex)
quando ocorre um erro em uma instrução try / catch.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.
fonte
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.
fonte
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
doSomething
mé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.
fonte
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!)
fonte
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.
fonte
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, ...:
Em geral, você precisa investir tempo e dinheiro , além disso, não há desvantagens, realmente!
fonte
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.
fonte
Duas coisas:
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.
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.
fonte
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.
fonte
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.
fonte