Fora das estruturas de injeção de dependência, a injeção de dependência (por injeção de construtor ou injeção de setter) é quase um jogo de soma zero: você diminui o acoplamento entre o objeto A e sua dependência B, mas agora qualquer objeto que precise de uma instância de A deve agora também construa o objeto B.
Você reduziu levemente o acoplamento entre A e B, mas reduziu o encapsulamento de A e aumentou o acoplamento entre A e qualquer classe que deve construir uma instância de A, acoplando-as às dependências de A também.
Portanto, a injeção de dependência (sem uma estrutura) é igualmente prejudicial, pois é útil.
No entanto, o custo extra é facilmente justificável: se o código do cliente sabe mais sobre como construir a dependência do que o próprio objeto, a injeção de dependência realmente reduz o acoplamento; por exemplo, um scanner não sabe muito sobre como obter ou construir um fluxo de entrada para analisar a entrada ou de qual fonte o código do cliente deseja analisar a entrada; portanto, a injeção de construtores de um fluxo de entrada é a solução óbvia.
O teste é outra justificativa, para poder usar dependências simuladas. Isso deve significar adicionar um construtor extra usado apenas para teste que permite que as dependências sejam injetadas: se você mudar seus construtores para sempre exigir que as dependências sejam injetadas, de repente, você precisará saber sobre as dependências das dependências de suas dependências para construir seu dependências diretas e você não pode realizar nenhum trabalho.
Pode ser útil, mas você definitivamente deve se perguntar para cada dependência, o benefício do teste vale o custo? Será que realmente quero zombar dessa dependência durante o teste?
Quando uma estrutura de injeção de dependência é adicionada e a construção de dependências é delegada não ao código do cliente, mas à estrutura, a análise de custo / benefício muda bastante.
Em uma estrutura de injeção de dependência, as compensações são um pouco diferentes; o que você está perdendo ao injetar uma dependência é a capacidade de saber facilmente em qual implementação você está confiando e mudando a responsabilidade de decidir em que dependência você está confiando em algum processo de resolução automatizado (por exemplo, se exigirmos um Foo @ Inject'ed , deve haver algo que o @Provides Foo e cujas dependências injetadas estejam disponíveis) ou algum arquivo de configuração de alto nível que prescreva qual provedor deve ser usado para cada recurso ou algum híbrido dos dois (por exemplo, pode haver ser um processo de resolução automatizado para dependências que podem ser substituídas, se necessário, usando um arquivo de configuração).
Como na injeção de construtor, acho que a vantagem de fazê-lo acaba, novamente, sendo muito semelhante ao custo de fazê-lo: você não precisa saber quem está fornecendo os dados nos quais confia e, se houver vários potenciais fornecedores, você não precisa saber a ordem preferida para procurar fornecedores, verifique se todos os locais que precisam dos dados verificam todos os fornecedores em potencial etc., porque tudo isso é tratado em alto nível pela injeção de dependência plataforma.
Embora eu pessoalmente não tenha muita experiência com estruturas de DI, minha impressão é de que elas oferecem mais benefício que custo, quando a dor de cabeça de encontrar o provedor correto dos dados ou serviços necessários tem um custo maior do que a dor de cabeça, quando algo falha, de não saber imediatamente localmente qual código forneceu os dados incorretos que causaram uma falha posterior no seu código.
Em alguns casos, outros padrões que obscurecem dependências (por exemplo, localizadores de serviço) já haviam sido adotados (e talvez também tenham provado seu valor) quando as estruturas de DI apareceram em cena, e as estruturas de DI foram adotadas porque ofereciam alguma vantagem competitiva, como exigir menos código padrão ou potencialmente fazendo menos para ocultar o provedor de dependência quando for necessário determinar qual provedor está realmente em uso.