Como evitar a loucura do construtor de Injeção de Dependência?

Eu acho que meus construtores estão começando a ficar assim:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... ) 

com lista de parâmetros cada vez maior. Como “Container” é meu contêiner de injeção de dependência, por que não posso fazer isso:

 public MyClass(Container con) 

para cada aula? Quais são as desvantagens? Se eu fizer isso, parece que estou usando uma estática glorificada. Por favor, compartilhe seus pensamentos sobre loucura por injeção de IoC e Dependência.

Você está certo de que, se você usar o contêiner como um Service Locator, é mais ou menos uma fábrica estática glorificada. Por várias razões , considero isso um anti-padrão .

Um dos maravilhosos benefícios da Injeção de Construtor é que ela torna as violações do Princípio da Responsabilidade Única manifestamente óbvias.

Quando isso acontece, é hora de refatorar os Serviços de Fachada . Em suma, crie uma nova interface mais grosseira que esconda a interação entre algumas ou todas as dependencies específicas que você precisa atualmente.

Eu não acho que seus construtores de class devem ter uma referência ao seu período de contêiner IOC. Isso representa uma dependência desnecessária entre sua class e o contêiner (o tipo de dependência que o COI está tentando evitar!).

A dificuldade de passar nos parâmetros não é o problema. O problema é que sua class está fazendo muito e deve ser dividida mais.

dependency injection pode atuar como um alerta precoce para as classs ficarem muito grandes, especificamente por causa da crescente dor de passar em todas as dependencies.

Eu me deparei com uma pergunta semelhante sobre dependency injection baseada em construtor e quão complexo ela estava passando em todas as dependencies.

Uma das abordagens, que usei no passado, é usar o padrão de fachada do aplicativo usando uma camada de serviço. Isso teria uma API grosseira. Se esse serviço depender de repositorys, ele usaria uma injeção setter das propriedades privadas. Isso requer a criação de uma fábrica abstrata e a lógica de criar os repositorys em uma fábrica.

Código detalhado com explicação pode ser encontrado aqui

Práticas recomendadas para IoC na camada de serviço complexa

Problema:

1) Construtor com lista de parâmetros cada vez maior.

2) Se a class é herdada (Ex: RepositoryBase ), então a mudança na assinatura do construtor causa mudanças nas classs derivadas.

Solução 1

Passar IoC Container ao construtor

Por quê

  • Não há mais lista de parâmetros crescente
  • A assinatura do construtor se torna simples

Por que não

  • Torna sua turma fortemente vinculada ao contêiner IoC. (Isso causa problemas quando 1. você deseja usar essa class em outros projetos onde você usa um contêiner IoC diferente. 2. você decide alterar o contêiner IoC)
  • Torna a aula menos descritiva. (Você não pode realmente olhar para o construtor de class e dizer o que ele precisa para funcionar.)
  • A class pode acessar potencialmente todo o serviço.

Solução 2

Crie uma class que agrupe todo o serviço e passe-o ao construtor

  public abstract class EFRepositoryBase { public class Dependency { public DbContext DbContext { get; } public IAuditFactory AuditFactory { get; } public Dependency( DbContext dbContext, IAuditFactory auditFactory) { DbContext = dbContext; AuditFactory = auditFactory; } } protected readonly DbContext DbContext; protected readonly IJobariaAuditFactory auditFactory; protected EFRepositoryBase(Dependency dependency) { DbContext = dependency.DbContext; auditFactory= dependency.JobariaAuditFactory; } } 

Classe derivada

  public class ApplicationEfRepository : EFRepositoryBase { public new class Dependency : EFRepositoryBase.Dependency { public IConcreteDependency ConcreteDependency { get; } public Dependency( DbContext dbContext, IAuditFactory auditFactory, IConcreteDependency concreteDependency) { DbContext = dbContext; AuditFactory = auditFactory; ConcreteDependency = concreteDependency; } } IConcreteDependency _concreteDependency; public ApplicationEfRepository( Dependency dependency) : base(dependency) { _concreteDependency = dependency.ConcreteDependency; } } 

Por quê

  • Adicionar nova dependência à class não afeta as classs derivadas
  • Classe é agnóstico do Container IoC
  • A class é descritiva (no aspecto de suas dependencies). Por convenção, se você quiser saber em que class A Depende, essa informação é acumulada em A.Dependency
  • Assinatura de construtor se torna simples

Por que não

  • precisa criar class adicional
  • o registro de serviço se torna complexo (você precisa registrar cada X.Dependency separadamente)
  • Conceitualmente o mesmo que passar o IoC Container
  • ..

A solução 2 é apenas uma bruta, se houver um argumento sólido contra ela, então o comentário descritivo seria apreciado

Esta é a abordagem que eu uso

 public class Hero { [Inject] private IInventory Inventory { get; set; } [Inject] private IArmour Armour { get; set; } [Inject] protected IWeapon Weapon { get; set; } [Inject] private IAction Jump { get; set; } [Inject] private IInstanceProvider InstanceProvider { get; set; } } 

Aqui está uma abordagem grosseira de como executar as injeções e executar o construtor depois de injetar valores. Este é um programa totalmente funcional.

 public class InjectAttribute : Attribute { } public class TestClass { [Inject] private SomeDependency sd { get; set; } public TestClass() { Console.WriteLine("ctor"); Console.WriteLine(sd); } } public class SomeDependency { } class Program { static void Main(string[] args) { object tc = FormatterServices.GetUninitializedObject(typeof(TestClass)); // Get all properties with inject tag List pi = typeof(TestClass) .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList(); // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it pi[0].SetValue(tc, new SomeDependency(), null); // Find the right constructor and Invoke it. ConstructorInfo ci = typeof(TestClass).GetConstructors()[0]; ci.Invoke(tc, null); } } 

Atualmente estou trabalhando em um projeto de hobby que funciona assim https://github.com/Jokine/ToolProject/tree/Core

Qual framework de injeção de dependência você está usando? Você já tentou usar injeção baseada em setter?

O benefício da injeção baseada em construtor é que ela parece natural para os programadores Java que não usam estruturas de DI. Você precisa de 5 coisas para inicializar uma class, então você tem 5 argumentos para o seu construtor. A desvantagem é o que você percebeu, fica difícil quando você tem muitas dependencies.

Com o Spring, você poderia passar os valores necessários com os setters e usar annotations @required para impor que eles sejam injetados. A desvantagem é que você precisa mover o código de boot do construtor para outro método e ter uma chamada Spring depois que todas as dependencies forem injetadas marcando-a com @PostConstruct. Não tenho certeza sobre outros frameworks, mas presumo que eles façam algo semelhante.

Ambas as formas funcionam, é uma questão de preferência.