Come evitare la follia del costruttore di dipendenza da dipendenza?

Trovo che i miei costruttori stiano cominciando ad assomigliare a questo:

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

con una lista di parametri sempre crescente. Dal momento che “Container” è il mio contenitore per le dipendenze, perché non posso semplicemente farlo:

 public MyClass(Container con) 

per ogni class? Quali sono gli aspetti negativi? Se lo faccio, mi sembra di usare una statica glorificata. Per favore condividi le tue opinioni sulla follia IoC e Dipendenza dall’iniezione.

Hai ragione che se usi il container come Localizzatore di servizio, è più o meno una fabbrica statica glorificata. Per molte ragioni, considero questo un anti-modello .

Uno dei meravigliosi vantaggi di Constructor Injection è che rende le violazioni del Principio di Responsabilità Unica evidentemente evidenti.

Quando ciò accade, è tempo di refactoring per i servizi di facciata . In breve, crea una nuova interfaccia più granulosa che nasconde l’interazione tra alcune o tutte le dipendenze a grana fine attualmente necessarie.

Non penso che i costruttori della tua class debbano avere un riferimento al periodo del tuo contenitore IOC. Ciò rappresenta una dipendenza non necessaria tra la class e il contenitore (il tipo di dipendenza che il CIO sta cercando di evitare!).

La difficoltà di passare i parametri non è il problema. Il problema è che la tua class sta facendo troppo e dovrebbe essere ulteriormente suddivisa.

L’iniezione di dipendenza può agire come un avvertimento precoce per le classi che diventano troppo grandi, in particolare a causa del crescente dolore derivante da tutte le dipendenze.

Mi sono imbattuto in una domanda simile sulla dipendenza da dipendenza basata su costruttore e sulla complessità che avrebbe dovuto superare in tutte le dipendenze.

Uno degli approcci, che ho usato in passato, è quello di utilizzare il modello di facciata dell’applicazione utilizzando un livello di servizio. Questo avrebbe un’API approssimativa. Se questo servizio dipende dai repository, utilizzerebbe un’iniezione setter delle proprietà private. Ciò richiede la creazione di una fabbrica astratta e lo spostamento della logica di creare i repository in una fabbrica.

Il codice dettagliato con spiegazione può essere trovato qui

Best practice per IoC in livello di servizio complesso

Problema:

1) Costruttore con lista parametri sempre crescente.

2) Se la class è ereditata (Es: RepositoryBase ), la modifica della firma del costruttore causa modifiche nelle classi derivate.

Soluzione 1

Passa il IoC Container al costruttore

Perché

  • Niente più liste di parametri sempre crescenti
  • La firma del costruttore diventa semplice

Perchè no

  • Ti rende class strettamente legata al contenitore IoC. (Questo causa problemi quando 1. si vuole usare quella class in altri progetti dove si usa un contenitore IoC diverso. 2. si decide di cambiare il contenitore IoC)
  • Ti rende una lezione meno descrittiva. (Non si può davvero guardare al costruttore di class e dire quello di cui ha bisogno per funzionare).
  • La class può accedere potenzialmente a tutti i servizi.

Soluzione 2

Crea una class che raggruppa tutti i servizi e passala al costruttore

  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 derivata

  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; } } 

Perché

  • L’aggiunta di una nuova dipendenza alla class non influisce sulle classi derivate
  • La class è agnostica di IoC Container
  • La class è descrittiva (in aspetto delle sue dipendenze). Per convenzione, se si desidera sapere a quale class A dipende, tali informazioni vengono accumulate in A.Dependency
  • La firma del costruttore diventa semplice

Perchè no

  • è necessario creare classi aggiuntive
  • la registrazione del servizio diventa complessa (è necessario registrare ogni X.Dependency separatamente)
  • Concettualmente come passare il IoC Container
  • ..

La soluzione 2 è solo un aspetto grezzo, tuttavia, se vi sono solide argomentazioni contro di essa, il commento descrittivo sarebbe apprezzato

Questo è l’approccio che 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; } } 

Ecco un approccio approssimativo su come eseguire le iniezioni ed eseguire il costruttore dopo aver iniettato i valori. Questo è un programma completamente funzionale.

 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); } } 

Attualmente sto lavorando a un progetto per hobby che funziona come questo https://github.com/Jokine/ToolProject/tree/Core

Quale struttura di iniezione di dipendenza stai usando? Hai provato a utilizzare l’iniezione basata sul setter?

Il vantaggio per l’iniezione basata su costruttore è che sembra naturale per i programmatori Java che non utilizzano i framework DI. Hai bisogno di 5 cose per inizializzare una class, quindi hai 5 argomenti per il tuo costruttore. Il rovescio della medaglia è ciò che hai notato, diventa ingombrante quando hai molte dipendenze.

Con Spring potresti invece passare i valori richiesti con setter e puoi usare le annotazioni richieste per far sì che vengano iniettate. Il rovescio della medaglia è che è necessario spostare il codice di inizializzazione dal costruttore in un altro metodo e avere una chiamata di Spring che dopo che tutte le dipendenze sono state iniettate contrassegnandolo con @PostConstruct. Non sono sicuro di altri framework, ma presumo che facciano qualcosa di simile.

Entrambi i modi funzionano, è una questione di preferenza.