Há um velho e velho debate em andamento sobre a melhor maneira de fazer a injeção de dependência.
O corte original da mola instanciava um objeto simples e, em seguida, injetava dependências através dos métodos setter.
Mas então uma grande contingência de pessoas insistiu que injetar dependências através de parâmetros de construtores era a maneira correta de fazê-lo.
Então, ultimamente, à medida que o uso da reflexão se tornou mais comum, definir os valores de membros privados diretamente, sem setters ou construtores args, tornou-se a raiva.
Portanto, seu primeiro construtor é consistente com a segunda abordagem para injeção de dependência. Ele permite que você faça coisas legais, como injetar zombarias para testes.
Mas o construtor sem argumento tem esse problema. Como instancia as classes de implementação para PaypalCreditCardProcessor
e DatabaseTransactionLog
, cria uma dependência difícil em tempo de compilação no PayPal e no banco de dados. É responsável por criar e configurar toda a árvore de dependências corretamente.
Imagine que o processador PayPay é um subsistema realmente complicado e, além disso, atrai muitas bibliotecas de suporte. Ao criar uma dependência em tempo de compilação nessa classe de implementação, você está criando um link inquebrável para toda a árvore de dependências. A complexidade do seu gráfico de objetos acabou de aumentar por uma ordem de magnitude, talvez duas.
Muitos desses itens na árvore de dependência serão transparentes, mas muitos deles também precisarão ser instanciados. As probabilidades são de que você não poderá instanciar apenas a PaypalCreditCardProcessor
.
Além da instanciação, cada um dos objetos precisará de propriedades aplicadas a partir da configuração.
Se você tiver apenas uma dependência da interface e permitir que uma fábrica externa crie e injete a dependência, eles cortam toda a árvore de dependências do PayPal, e a complexidade do seu código para na interface.
Existem outros benefícios, como especificar classes de implementações em configuração (ou seja, em tempo de execução em vez de tempo de compilação) ou ter uma especificação de dependência mais dinâmica que varia, por exemplo, por ambiente (teste, integração, produção).
Por exemplo, digamos que o PayPalProcessor tenha três objetos dependentes e cada uma dessas dependências tenha mais dois. E todos esses objetos precisam extrair propriedades da configuração. O código como está assumirá a responsabilidade de criar tudo isso, definir propriedades a partir da configuração, etc. etc. - todas as preocupações que a estrutura de DI resolverá.
Pode não parecer óbvio a princípio do que você está se protegendo usando uma estrutura de DI, mas isso aumenta e se torna dolorosamente óbvio ao longo do tempo. (lol, falo da experiência de ter tentado fazê-lo da maneira mais difícil)
...
Na prática, mesmo para um programa realmente minúsculo, acho que acabo escrevendo no estilo DI e divido as classes em pares de implementação / fábrica. Ou seja, se eu não estiver usando uma estrutura de DI como o Spring, apenas reunirei algumas classes simples de fábrica.
Isso fornece a separação de preocupações, para que minha classe possa fazer suas coisas, e a classe factory assume a responsabilidade de criar e configurar coisas.
Não é uma abordagem necessária, mas o FWIW
...
De maneira mais geral, o padrão DI / interface reduz a complexidade do seu código fazendo duas coisas:
Além disso, como a instanciação e configuração de objetos é uma tarefa bastante familiar, a estrutura de DI pode alcançar muitas economias de escala através de notação padronizada e usando truques como reflexão. A dispersão dessas mesmas preocupações nas aulas acaba adicionando muito mais confusão do que se poderia pensar.