Injeção de dependência através de construtores ou criadores de propriedades?

151

Estou refatorando uma classe e adicionando uma nova dependência a ela. A classe está atualmente assumindo suas dependências existentes no construtor. Portanto, para maior consistência, adiciono o parâmetro ao construtor.
Claro, existem algumas subclasses e ainda mais para testes de unidade, então agora estou jogando alterando todos os construtores para combinar, e isso está levando idades.
Isso me faz pensar que usar propriedades com setters é a melhor maneira de obter dependências. Eu não acho que dependências injetadas devam fazer parte da interface para construir uma instância de uma classe. Você adiciona uma dependência e agora todos os seus usuários (subclasses e qualquer pessoa que instancia você diretamente) repentinamente sabem disso. Parece uma quebra de encapsulamento.

Esse não parece ser o padrão com o código existente aqui, então estou procurando descobrir qual é o consenso geral, prós e contras de construtores versus propriedades. O uso de configuradores de propriedades é melhor?

Niall Connaughton
fonte

Respostas:

126

Bem, isto depende :-).

Se a classe não puder executar seu trabalho sem a dependência, adicione-a ao construtor. A classe precisa da nova dependência, portanto, você deseja que sua alteração desmonte as coisas. Além disso, a criação de uma classe que não é totalmente inicializada ("construção em duas etapas") é um anti-padrão (IMHO).

Se a classe puder funcionar sem a dependência, um levantador está bem.

sleske
fonte
11
Eu acho que em muitos casos é preferível usar o padrão Null Object e continuar exigindo as referências no construtor. Isso evita todas as verificações nulas e o aumento da complexidade ciclomática.
precisa
3
@ Mark: Bom ponto. No entanto, a pergunta era sobre adicionar uma dependência a uma classe existente. A manutenção de um construtor sem argumento permite compatibilidade com versões anteriores.
sleske
E quando a dependência é necessária para funcionar, mas uma injeção padrão dessa dependência geralmente é suficiente. Então essa dependência deve ser "substituível" por propriedade ou por uma sobrecarga de construtor?
Patrick Szalapski 26/08/10
@ Patrick: Por "a classe não pode fazer seu trabalho sem a dependência", eu quis dizer que não há um padrão razoável (por exemplo, a classe requer uma conexão com o banco de dados). Na sua situação, ambos funcionariam. Eu ainda normalmente optaria pela abordagem do construtor, porque reduz a complexidade (e se, por exemplo, o setter for chamado duas vezes)?
sleske
1
Uma boa descrição sobre isso aqui explorejava.com/different-types-of-bean-injection-in-spring
Eldhose Abraham
21

Os usuários de uma classe devem saber sobre as dependências de uma determinada classe. Se eu tivesse uma classe que, por exemplo, estivesse conectada a um banco de dados e não fornecesse um meio de injetar uma dependência da camada de persistência, um usuário nunca saberia que uma conexão ao banco de dados teria que estar disponível. No entanto, se eu alterar o construtor, informarei aos usuários que há uma dependência da camada de persistência.

Além disso, para evitar que você precise alterar todo uso do construtor antigo, basta aplicar o encadeamento do construtor como uma ponte temporária entre o construtor antigo e o novo.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Um dos pontos da injeção de dependência é revelar quais dependências a classe possui. Se a classe tiver muitas dependências, talvez seja hora de uma refatoração: Todo método da classe usa todas as dependências? Caso contrário, é um bom ponto de partida para ver onde a classe pode ser dividida.

Doctor Blue
fonte
Obviamente, o encadeamento de construtores só funciona se houver um valor padrão razoável para o novo parâmetro. Mas caso contrário, você não pode evitar quebrar as coisas de qualquer maneira ...
sleske
Normalmente, você usaria o que estava usando no método antes da injeção de dependência como o parâmetro padrão. Idealmente, isso tornaria a adição do novo construtor uma refatoração limpa, pois o comportamento da classe não mudaria.
Doutor Azul
1
Entendo seu ponto de vista sobre gerenciamento de dependências, como conexões com o banco de dados. Acho que o problema no meu caso é que a classe à qual estou adicionando uma dependência possui várias subclasses. Em um mundo de contêineres do IOC em que a propriedade seria configurada pelo contêiner, o uso do levantador pelo menos aliviaria a pressão na duplicação da interface do construtor entre todas as subclasses.
Niall Connaughton
17

Obviamente, colocar o construtor significa que você pode validar tudo de uma vez. Se você atribuir itens a campos somente leitura, terá algumas garantias sobre as dependências do seu objeto desde o momento da construção.

É uma verdadeira dor adicionar novas dependências, mas pelo menos dessa maneira o compilador continua reclamando até que esteja correto. O que é uma coisa boa, eu acho.

Joe
fonte
Mais um para isso. Além disso, esta extremamente reduz o perigo de dependências circulares ...
gilgamash
11

Se você tiver um grande número de dependências opcionais (o que já é um cheiro), provavelmente a injeção de setter é o caminho a seguir. A injeção de construtores revela melhor suas dependências.

epitka
fonte
11

A abordagem geral preferida é usar a injeção do construtor o máximo possível.

A injeção de construtor indica exatamente quais são as dependências necessárias para que o objeto funcione corretamente - nada é mais irritante do que atualizar um objeto e fazer com que ele falhe ao chamar um método, porque alguma dependência não está definida. O objeto retornado por um construtor deve estar em um estado de trabalho.

Tente ter apenas um construtor, ele mantém o design simples e evita ambiguidade (se não for para humanos, para o contêiner DI).

Você pode usar a injeção de propriedade quando tiver o que Mark Seemann chama de padrão local em seu livro "Injeção de Dependências no .NET": a dependência é opcional porque você pode fornecer uma boa implementação de trabalho, mas deseja permitir que o chamador especifique outra se necessário.

(Resposta anterior abaixo)


Eu acho que a injeção de construtor é melhor se a injeção for obrigatória. Se isso adicionar muitos construtores, considere usar fábricas em vez de construtores.

A injeção do setter é boa se a injeção for opcional ou se você quiser alterá-la até a metade. Geralmente não gosto de levantadores, mas é uma questão de gosto.

Philippe
fonte
Eu diria que geralmente mudar a injeção no meio é ruim (porque você está adicionando um estado oculto ao seu objeto). Mas nenhuma regra w / o exceção, é claro ...
sleske
Sim, foi por isso que eu disse que não gostava muito de setter ... Eu gosto da abordagem do construtor, pois ela não pode ser alterada.
Philippe
"Se isso adicionar muitos construtores, considere usar fábricas em vez de construtores". Basicamente, você adia qualquer exceção de tempo de execução e pode até dar errado e acabar ficando preso com uma implementação do localizador de serviço.
MeTitus 9/09/16
@ Marco, que é a minha resposta anterior e você está certo. Se houver muitos construtores, eu diria que a classe faz muitas coisas :-) Ou considere uma fábrica abstrata.
Philippe
7

É em grande parte uma questão de gosto pessoal. Pessoalmente, eu prefiro a injeção do setter, porque acredito que ela oferece mais flexibilidade na maneira de substituir implementações em tempo de execução. Além disso, construtores com muitos argumentos não são limpos na minha opinião, e os argumentos fornecidos em um construtor devem ser limitados a argumentos não opcionais.

Desde que a interface de classes (API) seja clara no que precisa para executar sua tarefa, você estará bem.

nkr1pt
fonte
por favor, especifique por que você está com votos negativos?
Nkr1pt 1/10/09
3
Sim, construtores com muitos argumentos são ruins. É por isso que você refatora classes com muitos parâmetros de construtor :-).
Sleske #
10
@ nkr1pt: A maioria das pessoas (inclusive eu) concorda que a injeção do setter é ruim se permitir que você crie uma classe que falha no tempo de execução se a injeção não for feita. Acredito, portanto, que alguém se opôs à sua afirmação de que é gosto pessoal.
Sleske #
7

Pessoalmente, prefiro o "padrão" Extrair e Substituir a injetar dependências no construtor, principalmente pelo motivo descrito em sua pergunta. Você pode definir as propriedades como virtuale, em seguida, substituir a implementação em uma classe testável derivada.

Russ Cam
fonte
1
Eu acho que o nome oficial do padrão é "Método do Modelo".
Davidjnelson
6

Prefiro injeção de construtor porque ajuda a "impor" os requisitos de dependência de uma classe. Se é na c'tor, um consumidor tem para definir os objetos para obter o aplicativo para compilar. Se você usar a injeção de incubadora, eles talvez não saibam que têm um problema até o tempo de execução - e, dependendo do objeto, pode ser tarde no tempo de execução.

Eu ainda uso a injeção de setter de tempos em tempos, quando o objeto injetado talvez precise de um monte de trabalho, como inicialização.

ctacke
fonte
6

Eu prefiro a injeção de construtores, porque isso parece mais lógico. É como dizer que minha turma exige que essas dependências façam seu trabalho. Se é uma dependência opcional, as propriedades parecem razoáveis.

Também uso a injeção de propriedades para definir coisas às quais o contêiner não possui referências, como uma exibição do ASP.NET em um apresentador criado usando o contêiner.

Eu não acho que isso quebra o encapsulamento. O funcionamento interno deve permanecer interno e as dependências lidam com uma preocupação diferente.

David Kiff
fonte
Obrigado pela sua resposta. Certamente parece que o construtor é a resposta popular. No entanto, acho que quebra o encapsulamento de alguma forma. Antes da injeção de dependência, a classe declararia e instanciaria quaisquer tipos concretos necessários para realizar seu trabalho. Com o DI, as subclasses (e quaisquer instanciadores manuais) agora sabem quais ferramentas uma classe base está usando. Você adiciona uma nova dependência e agora precisa encadear a instância de todas as subclasses, mesmo que elas não precisem usar a dependência.
Niall Connaughton
Acabei de escrever uma boa resposta longa e a perdi devido a uma exceção neste site !! :( Em resumo, a classe básica é geralmente usada para reutilizar a lógica. Essa lógica poderia facilmente entrar na subclasse ... para que você possa pensar na classe básica e na subclasse = uma preocupação, que depende de vários objetos externos, que . fazer trabalhos diferentes O fato de você ter dependências, não significa que você precisa expor qualquer coisa que você teria anteriormente mantidas em sigilo.
David Kiff
3

Uma opção que pode valer a pena considerar é a composição de múltiplas dependências complexas a partir de dependências únicas simples. Ou seja, defina classes extras para dependências compostas. Isso torna as coisas um pouco mais fáceis de injeção do construtor WRT - menos parâmetros por chamada - enquanto mantém a coisa de fornecer todas as dependências para instanciar.

É claro que faz mais sentido se houver algum tipo de agrupamento lógico de dependências, portanto o composto é mais do que um agregado arbitrário e faz mais sentido se houver vários dependentes para uma única dependência composta - mas o bloco de parâmetros "padrão" tem existe há muito tempo, e a maioria dos que eu vi foram bastante arbitrários.

Pessoalmente, porém, sou mais fã do uso de métodos / configuradores de propriedades para especificar dependências, opções etc. Os nomes das chamadas ajudam a descrever o que está acontecendo. É uma boa idéia fornecer exemplos de trechos de como é configurá-lo e garantir que a classe dependente faça verificações de erro suficientes. Você pode querer usar um modelo de estado finito para a configuração.

Steve314
fonte
3

Recentemente, tive uma situação em que tinha várias dependências em uma classe, mas apenas uma delas dependia necessariamente em cada implementação. Como as dependências de acesso a dados e registro de erros provavelmente só seriam alteradas para fins de teste, adicionei parâmetros opcionais para essas dependências e forneci implementações padrão dessas dependências no meu código de construtor. Dessa maneira, a classe mantém seu comportamento padrão, a menos que seja substituída pelo consumidor da classe.

O uso de parâmetros opcionais só pode ser realizado em estruturas que os suportam, como o .NET 4 (para C # e VB.NET, embora o VB.NET sempre os tenha). Obviamente, você pode obter funcionalidade semelhante simplesmente usando uma propriedade que pode ser reatribuída pelo consumidor da sua classe, mas você não obtém a vantagem da imutabilidade fornecida por ter um objeto de interface privada atribuído a um parâmetro do construtor.

Tudo isso dito, se você estiver introduzindo uma nova dependência que deve ser fornecida por todos os consumidores, precisará refatorar o construtor e todo o código que consome a sua classe. Minhas sugestões acima realmente se aplicam apenas se você tiver o luxo de poder fornecer uma implementação padrão para todo o seu código atual, mas ainda fornecer a capacidade de substituir a implementação padrão, se necessário.

Ben McCormack
fonte
1

Este é um post antigo, mas se for necessário no futuro, talvez seja útil:

https://github.com/omegamit6zeichen/prinject

Eu tive uma idéia semelhante e surgiu com essa estrutura. Provavelmente está longe de estar completo, mas é uma ideia de uma estrutura focada na injeção de propriedades

Markus
fonte
0

Depende de como você deseja implementar. Prefiro injeção de construtor sempre que sentir que os valores que entram na implementação não mudam com frequência. Por exemplo: se a estratégia de compnay for com o servidor oracle, configurarei meus valores de fonte de dados para um bean obtendo conexões via injeção de construtor. Caso contrário, se meu aplicativo for um produto e houver chances de ele se conectar a qualquer banco de dados do cliente, eu implementaria essa configuração de banco de dados e a implementação de várias marcas através da injeção de setter. Acabei de dar um exemplo, mas existem maneiras melhores de implementar os cenários mencionados acima.

Ramarao Gangina
fonte
1
Mesmo ao codificar internamente, sempre codifico do ponto de vista de que sou um contratado independente que desenvolve código que deve ser redistribuível. Presumo que seja de código aberto. Dessa maneira, asseguro que o código seja modular e conectável e siga os princípios do SOLID.
24316 Fred Fred
0

A injeção de construtor revela explicitamente as dependências, tornando o código mais legível e menos propenso a erros de tempo de execução sem tratamento, se os argumentos são verificados no construtor, mas na verdade se resume à opinião pessoal e, quanto mais você usar o DI, mais você usará o DI. tendem a balançar para frente e para trás de uma maneira ou de outra, dependendo do projeto. Pessoalmente, tenho problemas com o cheiro do código, como construtores com uma longa lista de argumentos, e sinto que o consumidor de um objeto deve conhecer as dependências para poder usá-lo de qualquer maneira, portanto, é possível usar injeção de propriedade. Não gosto da natureza implícita da injeção de propriedades, mas acho mais elegante, resultando em um código com aparência mais limpa. Por outro lado, a injeção de construtores oferece um maior grau de encapsulamento,

Escolha a injeção por construtor ou por propriedade com base no seu cenário específico. E não pense que você precisa usar o DI apenas porque parece necessário e isso evitará maus cheiros de design e código. Às vezes, não vale a pena o esforço de usar um padrão se o esforço e a complexidade superam os benefícios. Mantenha simples.

David Spenard
fonte