230

trovo che i miei costruttori stanno cominciando a guardare in questo modo:Come evitare la follia del costruttore di dipendenze dell'iniezione?

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

con sempre maggiore lista di parametri. Dal momento che "Container" è il mio contenitore per le dipendenze, perché non posso farlo:

public MyClass(Container con) 

per ogni classe? 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.

+53

perché stai passando il contenitore? Penso che potresti fraintendere IOC –

+28

Se i tuoi costruttori richiedono più o più parametri potresti fare troppo in quelle classi. –

+31

Non è così che si fa l'iniezione del costruttore. Gli oggetti non conoscono affatto il contenitore IoC, né dovrebbero. – duffymo

risposta

329

Hai ragione che se si utilizza il contenitore come localizzatore di servizio, è più o meno una fabbrica statica glorificata. Per molti motivi I consider this an anti-pattern.

Uno dei meravigliosi vantaggi di Constructor Injection è che rende evidenti le violazioni dello Single Responsibility Principle.

Quando ciò accade, è il momento di refactor to Facade Services. In breve, creare un nuovo, più a grana grossa interfaccia che nasconde l'interazione tra alcune o tutte le dipendenze a grana fine attualmente richiesti.

+7

+1 per quantificare lo sforzo di refactoring in un singolo concetto; fantastico :) –

+33

per davvero? hai appena creato un riferimento indiretto per spostare quei parametri in un'altra classe, ma sono ancora lì! solo più complicato trattare con loro. – irreputable

+7

+1 per menzionare che è un anti-pattern. – fastcodejava

53

non credo che i vostri costruttori della classe dovrebbero avere un riferimento al periodo di contenitore del CIO. Ciò rappresenta una dipendenza non necessaria tra la classe e il contenitore (il tipo di dipendenza che il CIO sta cercando di evitare!).

+0

+1 A seconda di un contenitore IoC, è difficile cambiare quel contenitore in seguito senza cambiare il codice in tutte le altre classi – Tseng

+0

Come implementerai IOC senza avere parametri di interfaccia sui costruttori ? Sto leggendo il tuo post sbagliato? –

22

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

Dipendenza L'iniezione può costituire un avvertimento precoce per le classi che diventano troppo grandi, in particolare a causa del crescente dolore derivante da tutte le dipendenze.

+20

Correggimi se sbaglio, ma ad un certo punto devi "incollare tutto insieme", e quindi devi ottenere più di alcune dipendenze per quello. Ad esempio, nel livello Visualizza, quando si creano modelli e dati per essi, è necessario acquisire tutti i dati da varie dipendenze (ad esempio "servizi") e quindi inserire tutti questi dati nel modello e sullo schermo. Se la mia pagina web contiene 10 diversi "blocchi" di informazioni, ho bisogno di 10 classi diverse per fornirmi tali dati. Quindi ho bisogno di 10 dipendenze nella mia classe View/Template? – Andrew

+2

"Correggimi se sbaglio". Beh, sembra che tu non abbia torto! –

-7

Quale quadro 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 classe, quindi hai 5 argomenti per il tuo costruttore. Il rovescio della medaglia è ciò che hai notato, diventa ingombrante quando hai molte dipendenze.

Con Spring è possibile passare invece i valori richiesti con i setter e è possibile utilizzare 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 metodi di lavoro, è una questione di preferenze.

+17

Il motivo dell'iniezione del costruttore è di rendere le dipendenze ovvie, non perché sembra più naturale per gli sviluppatori java. –

+7

Commento tardivo, ma questa risposta mi ha fatto ridere :) –

+0

+1 per iniezioni basate su setter. Se ho dei servizi e dei repository definiti nella mia classe è piuttosto dannatamente ovvio che siano delle dipendenze. Non ho bisogno di scrivere massicci costruttori di VB6, e di creare codice stupido nel costruttore. È piuttosto ovvio quali siano le dipendenze nei campi richiesti. – ppumkin

3

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

Uno dei approccio, che ho usato in passato, è quello di utilizzare il modello di facciata 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.

codice dettagliata con la spiegazione può essere trovata qui

Best practices for IoC in complex service layer

-2

Problema:

1) Costruttore con sempre maggiore lista di parametri.

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

Soluzione 1

Passo IoC Container al costruttore

Perché

  • Non più sempre crescente lista di parametri
  • firma Costruttori diventa semplice

Perché non

  • Ti fa classe strettamente accoppiati al contenitore CIO. (Che provoca problemi quando 1. si desidera utilizzare tale classe in altri progetti in cui si utilizza un contenitore IoC diverso 2. si decide di modificare il contenitore IoC)
  • Rende la classe meno descrittiva. (Non si può davvero guardare al costruttore di classi e dire cosa serve per il funzionamento.)
  • La classe può accedere potenzialmente a tutti i servizi.

Soluzione 2

Creare una classe che raggruppa tutti i servizi e passarlo 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é

  • classi Aggiunta nuova dipendenza di classe non influenza deriva
  • Class è agnostica di IoC Container
  • Class è descrittivo (in aspetto delle sue dipendenze).Per convenzione, se volete sapere quale classe A Dipende, che l'informazione si accumula nei A.Dependency
  • firma Constructor diventa semplice

Perché non

  • necessità di creare classe aggiuntiva
  • La registrazione del servizio
  • diventa complessa (È necessario registrarsi ogni X.Dependency separatamente)
  • Concettualmente stesso passando IoC Container
  • ..

Soluzione 2 è solo un grezzo però, se v'è solido argomento contrario, allora commento descrittivo sarebbe apprezzato

0

Questo è il 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 grezzo come eseguire le iniezioni e correre costruttore dopo inj 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<PropertyInfo> 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 su un progetto hobby, che funziona in questo modo https://github.com/Jokine/ToolProject/tree/Core