As dependências de injeção devem ser feitas no ctor ou por método?

16

Considerar:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

contra:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Enquanto a injeção de Ctor dificulta a extensão (qualquer código que chame o ctor precisará ser atualizado quando novas dependências são adicionadas), e a injeção no nível do método parece permanecer mais encapsulada da dependência no nível da classe e não consigo encontrar outros argumentos a favor / contra essas abordagens .

Existe uma abordagem definitiva a ser tomada para injeção?

(NB, procurei informações sobre isso e tentei tornar essa pergunta objetiva.)

StuperUser
fonte
4
Se suas classes forem coesas, muitos métodos diferentes estariam usando os mesmos campos. Isso deve lhe dar a resposta que você procura ...
Oded
@Oded Bom ponto, a coesão influenciaria fortemente a decisão. Se uma classe é, digamos, coleções de métodos auxiliares, cada uma com dependências diferentes (aparentemente não coesas, mas logicamente semelhantes) e outra é altamente coesa, elas devem usar a abordagem mais benéfica. No entanto, isso causaria inconsistência em uma solução.
precisa saber é o seguinte
Relevante para os exemplos que eu dei: en.wikipedia.org/wiki/False_dilemma
StuperUser 3/12/12

Respostas:

3

Depende, se o objeto injetado for usado por mais de um método na classe e injetar nele em cada método não fizer sentido, você precisará resolver as dependências para cada chamada de método e, mas se a dependência for usada apenas por um método injetado no construtor não é bom, mas como você está alocando recursos que nunca poderiam ser usados.

nohros
fonte
15

Certo, primeiro, vamos lidar com "Qualquer código que chame o ctor precisará ser atualizado quando novas dependências forem adicionadas"; Para ser claro, se você está injetando dependências e tem algum código chamando new () em um objeto com dependências, está fazendo errado .

Seu contêiner de DI deve poder injetar todas as dependências relevantes, para que você não precise se preocupar em alterar a assinatura do construtor, para que o argumento não seja realmente válido.

Quanto à idéia de injeção por método versus por classe, há dois problemas principais com a injeção por método.

Uma questão é que os métodos da sua classe devem compartilhar dependências. É uma maneira de ajudar a garantir que suas classes sejam separadas de maneira eficaz. Se você vir uma classe com um grande número de dependências (provavelmente mais de 4-5), essa classe será a principal candidata. para refatorar em duas classes.

O próximo problema é que, para "injetar" as dependências, por método, você precisará passá-las para a chamada do método. Isso significa que você terá que resolver as dependências antes da chamada do método, portanto, provavelmente terá um monte de código como este:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Agora, digamos que você chamará esse método em 10 locais em torno do seu aplicativo: você terá 10 desses trechos. Em seguida, suponha que você precise adicionar outra dependência ao DoStuff (): você precisará alterar esse trecho 10 vezes (ou agrupá-lo em um método, nesse caso, você está apenas replicando o comportamento do DI manualmente, o que é um desperdício). de tempo).

Portanto, o que você basicamente fez lá fez com que suas classes que utilizavam DI tivessem conhecimento de seu próprio contêiner DI, o que é uma péssima idéia , pois rapidamente leva a um design desajeitado e difícil de manter.

Compare isso à injeção do construtor; Na injeção de construtor, você não está vinculado a um contêiner de DI específico e nunca é diretamente responsável por cumprir as dependências de suas classes; portanto, a manutenção é bastante simples.

Parece-me que você está tentando aplicar a IoC a uma classe que contém vários métodos auxiliares não relacionados, embora seja melhor dividir a classe auxiliar em várias classes de serviço com base no uso e usar o auxiliar para delegar o chamadas. Essa ainda não é uma ótima abordagem (ajudante classificado com métodos que fazem algo mais complexo do que apenas lidar com os argumentos passados ​​para eles geralmente são apenas classes de serviço mal escritas), mas pelo menos manterá seu design um pouco mais limpo .

(NB: Eu já fiz a abordagem que você está sugerindo antes, e foi uma péssima ideia que eu não repeti desde então. Acabei tentando separar classes que realmente não precisavam ser separadas e acabei com um conjunto de interfaces em que cada chamada de método exigia uma seleção quase fixa de outras interfaces. Era um pesadelo para manter.)

Ed James
fonte
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Se você está testando por unidade um método em uma classe, precisa instancia-lo ou usa o DI para instanciar seu código em teste?
precisa saber é o seguinte
@StuperUser Unit testing é um cenário um pouco diferente, meu comentário realmente se aplica apenas ao código de produção. Como você criará dependências simuladas (ou stub) para seus testes por meio de uma estrutura de simulação, cada uma com um comportamento pré-configurado e um conjunto de premissas e afirmações, será necessário injetá-las manualmente.
Ed James
1
Esse é o código que eu estou falando quando digo "any code calling the ctor will need updating", meu código de produção não chama os ctors. Por estas razões.
precisa saber é o seguinte
2
@StuperUser Fair o suficiente, mas você continuará chamando os métodos com as dependências necessárias, o que significa que o restante da minha resposta ainda permanece! Para ser sincero, concordo com o problema com a injeção de construtores e forçar todas as dependências, de uma perspectiva de teste de unidade. Eu costumo usar injeção de propriedade, pois isso permite que você injete apenas as dependências que você precisa para um teste, o que eu acho ser basicamente outro conjunto de chamadas implícitas Assert.WasNotCalled sem precisar realmente escrever nenhum código! : D
Ed James
3

Existem vários tipos de inversão de controle , e sua pergunta propõe apenas dois (você também pode usar o factory para injetar dependência).

A resposta é: depende da necessidade. Às vezes é melhor usar um tipo e outro tipo diferente.

Eu pessoalmente gosto de injetores de construtores, pois o construtor existe para inicializar completamente o objeto. Ter que chamar um levantador mais tarde faz com que o objeto não seja totalmente construído.

BЈовић
fonte
3

Você perdeu a que pelo menos para mim é a injeção de propriedade mais flexível. Eu uso o Castle / Windsor e tudo o que preciso fazer é adicionar uma nova propriedade automática à minha classe e obter o objeto injetado sem problemas e sem interromper nenhuma interface.

Otávio Décio
fonte
Estou acostumado a aplicativos da web e se essas classes estão na camada Domínio, chamada pela interface do usuário, que tem suas dependências já resolvidas de maneira semelhante à Injeção de propriedades. Eu estou querendo saber como lidar com classes para as quais eu posso passar os objetos injetados.
StuperUser
Também gosto de injeção de propriedade, simplesmente para que você ainda possa usar parâmetros normais do construtor com um contêiner de DI, diferenciando automaticamente as dependências resolvidas e não resolvidas. No entanto, como constructor- e propriedade de injecção são funcionalmente idênticos para esse cenário que eu escolhi para não mencioná-lo;)
Ed James
A injeção de propriedade força todos os objetos a serem mutáveis ​​e cria instâncias em um estado inválido. Por que isso seria preferível?
Chris Pitman
2
@ Chris Na verdade, em condições normais, a injeção do construtor e da propriedade atua de maneira idêntica, com o objeto sendo totalmente injetado durante a resolução. O único momento em que pode ser considerado "inválido" é durante o teste de unidade, quando você não usará o contêiner de DI para instanciar a implementação. Veja o meu comentário na minha resposta para a minha opinião sobre por que isso é realmente benéfico. Compreendo que a mutabilidade possa ser um problema, mas, realisticamente, você só fará referência a classes resolvidas por meio de suas interfaces, o que não expõe as dependências a serem editadas.
Ed James