No nível da turma, é fácil.
'Injeção de dependência' simplesmente responde à pergunta "como vou encontrar meus colaboradores" com "eles são empurrados contra você - você não precisa ir buscá-los". (É semelhante a - mas não é o mesmo que - 'Inversão de controle', onde a pergunta "como ordenarei minhas operações sobre minhas entradas?" Tem uma resposta semelhante).
O único benefício que seus colaboradores pressionam é que permite que o código do cliente use sua classe para compor um gráfico de objeto que atenda às suas necessidades atuais ... você não determinou arbitrariamente a forma e a mutabilidade do gráfico ao decidir em particular os tipos concretos e o ciclo de vida de seus colaboradores.
(Todos esses outros benefícios, de testabilidade, de acoplamento flexível, etc., decorrem amplamente do uso de interfaces e não tanto da dependência-injeção-ness, embora o DI promova naturalmente o uso de interfaces).
Vale a pena notar que, se você evitar instanciar seus próprios colaboradores, sua classe deverá, portanto, obter seus colaboradores de um construtor, uma propriedade ou um argumento de método (essa última opção geralmente é ignorada, a propósito ... nem sempre faz sentido que os colaboradores de uma classe façam parte de seu 'estado').
E isso é uma coisa boa.
No nível do aplicativo ...
Tanto para a visão por classe das coisas. Digamos que você tenha várias classes que seguem a regra "não instancia seus próprios colaboradores" e deseja fazer uma aplicação a partir delas. A coisa mais simples a fazer é usar um bom código antigo (uma ferramenta realmente útil para invocar construtores, propriedades e métodos!) Para compor o gráfico de objetos que você deseja e lançar alguma entrada nele. (Sim, alguns desses objetos em seu gráfico serão fábricas de objetos, que foram passadas como colaboradores para outros objetos de longa duração no gráfico, prontos para o serviço ... você não pode pré-construir cada objeto! )
... sua necessidade de 'flexivelmente' configurar o gráfico de objetos do seu aplicativo ...
Dependendo dos seus outros objetivos (não relacionados ao código), convém dar ao usuário final algum controle sobre o gráfico de objetos assim implantado. Isso o leva na direção de um esquema de configuração, de um tipo ou de outro, seja um arquivo de texto de seu próprio design, com alguns pares de nome / valor, um arquivo XML, uma DSL personalizada, uma linguagem declarativa de descrição gráfica, como YAML, uma linguagem de script imperativa, como JavaScript, ou outra coisa apropriada para a tarefa em questão. O que for necessário para compor um gráfico de objeto válido, de maneira a atender às necessidades de seus usuários.
... pode ser uma força de projeto significativa.
Na circunstância mais extrema desse tipo, você pode optar por adotar uma abordagem muito geral e fornecer aos usuários finais um mecanismo geral para 'conectar' o gráfico de objetos de sua escolha e até permitir que eles forneçam realizações concretas de interfaces para o tempo de execução! (Sua documentação é uma jóia brilhante, seus usuários são muito inteligentes, familiarizados com pelo menos o contorno geral do gráfico de objetos do seu aplicativo, mas não têm um compilador à mão). Teoricamente, esse cenário pode ocorrer em algumas situações da empresa.
Nesse caso, você provavelmente possui uma linguagem declarativa que permite que seus usuários expressem qualquer tipo, a composição de um gráfico de objetos desses tipos e uma paleta de interfaces com as quais o mítico usuário final pode misturar e combinar. Para reduzir a carga cognitiva de seus usuários, você prefere uma abordagem de 'configuração por convenção', para que eles só precisem intervir e substituir o fragmento de objeto-gráfico-interesse, em vez de lutar com a coisa toda.
Seu pobre idiota!
Como você não gostava de escrever tudo isso sozinho (mas sério, confira uma ligação YAML para o seu idioma), você está usando algum tipo de estrutura DI.
Dependendo da maturidade dessa estrutura, você pode não ter a opção de usar injeção de construtor, mesmo quando faz sentido (os colaboradores não mudam durante a vida útil de um objeto), forçando-o a usar a Injeção de Setter (mesmo quando os colaboradores não mudam durante o tempo de vida de um objeto, e mesmo quando não há realmente uma razão lógica pela qual todas as implementações concretas de uma interface devem ter colaboradores de um tipo específico). Nesse caso, você está atualmente em um inferno de fortes acoplamentos, apesar de ter diligentemente 'usado interfaces' em toda a sua base de código - horror!
Felizmente, você usou uma estrutura de DI que oferece a opção de injeção de construtor, e seus usuários ficam um pouco irritados com você por não gastar mais tempo pensando nas coisas específicas que eles precisavam para configurar e fornecendo a eles uma interface do usuário mais adequada para a tarefa à mão. (Apesar de ser justo, você provavelmente tentou pensar em uma maneira, mas o JavaEE decepcionou e você teve que recorrer a esse truque horrível).
Nota de inicialização
Em nenhum momento você está usando o Google Guice, que fornece ao codificador uma maneira de dispensar a tarefa de compor um gráfico de objetos com código ... escrevendo código. Argh!