Iniezione delle dipendenze: iniezione di campo contro iniezione del costruttore?

56

So che questo è un dibattito acceso e le opinioni tendono a cambiare nel tempo per quanto riguarda la migliore pratica di approccio.

Ho usato esclusivamente l'iniezione di campo per le mie lezioni, fino a quando ho iniziato a leggere su diversi blog (ex: petrikainulainen e schauderhaft e fowler ) sui vantaggi dell'iniezione del costruttore. Da allora ho cambiato le mie metodologie per utilizzare l'iniezione del costruttore per le dipendenze richieste e l'iniezione setter per dipendenze opzionali.

Tuttavia, recentemente ho iniziato un dibattito con l'autore di JMockit - una struttura di scherno - in cui vede il costruttore e l'ampli; Iniezione di setter come cattiva pratica e indica che la comunità JEE è d'accordo con lui.

Nel mondo di oggi, c'è un modo preferito di fare l'iniezione? L'iniezione di campo è preferibile?

Passato a iniettare il costruttore dall'iniezione sul campo negli ultimi due anni, lo trovo molto più chiaro da usare, ma mi chiedo se dovrei riesaminare il mio punto di vista. L'autore di JMockit (Rogério Liesenfeld) , è chiaramente esperto in DI, quindi mi sento obbligato a rivedere il mio approccio dato che si sente così strongmente contro l'iniezione costruttore / setter.

    
posta Eric B. 23.10.2015 - 18:47
fonte

5 risposte

47

L'iniezione sul campo è un po 'troppo "azione spettrale a distanza" per i miei gusti.

Considera l'esempio che hai fornito nel post di Google Gruppi:

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

Quindi sostanzialmente quello che stai dicendo è che "ho questa classe con stato privato, a cui ho allegato @injectable annotazioni, il che significa che lo stato può essere automaticamente popolato da qualche agente dall'esterno, anche se il mio stato è stato dichiarato privato "

Capisco le motivazioni per questo. È un tentativo di evitare gran parte della cerimonia che è inerente alla creazione di una classe in modo corretto. Fondamentalmente, quello che si sta dicendo è che "Sono stanco di scrivere tutto questo boilerplate, quindi ho intenzione di annotare tutto il mio stato e lasciare che il contenitore DI si occupi di impostarlo per me."

È un punto di vista perfettamente valido. Ma è anche una soluzione alternativa per le funzionalità linguistiche che probabilmente non dovrebbero essere risolte. Inoltre, perché fermarsi qui? Tradizionalmente, DI si è basato su ogni classe che ha un'interfaccia compagna. Perché non eliminare tutte quelle interfacce con le annotazioni?

Considera l'alternativa (questo sarà C #, perché lo conosco meglio, ma probabilmente c'è un equivalente esatto in Java):

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

Già conosco alcune cose su questa classe. È una classe immutabile; lo stato può essere impostato solo nel costruttore (riferimenti, in questo caso particolare). E poiché tutto deriva da un'interfaccia, posso sostituire le implementazioni nel costruttore, che è il punto in cui arrivano i tuoi scherzi.

Ora tutto ciò che il mio contenitore DI deve fare è riflettere sul costruttore per determinare quali oggetti ha bisogno di rinnovare. Ma questa riflessione viene fatta su un membro pubblico, in un modo di prima classe; cioè i metadati sono già parte della classe, essendo stati dichiarati nel costruttore, un metodo il cui esplicito scopo è fornire alla classe le dipendenze di cui ha bisogno.

Certo, questo è un gran numero di regole, ma è così che è stato progettato il linguaggio. Le annotazioni sembrano un trucco sporco per qualcosa che avrebbe dovuto essere incorporato nella lingua stessa.

    
risposta data 23.10.2015 - 21:11
fonte
25

L'argomento di boilplate di inizializzazione di test inferiore è valido, ma ci sono altre preoccupazioni che devono essere prese in considerazione. Prima di tutto, devi rispondere a una domanda:

Voglio che la mia classe sia istantanea solo con la riflessione?

Utilizzare le iniezioni sul campo significa restringere la compatibilità di una classe agli ambienti di iniezione delle dipendenze che istanziano gli oggetti usando la riflessione e supportano questi particolari annotazioni di iniezione. Alcune piattaforme basate sul linguaggio Java non supportano nemmeno la riflessione ( GWT ), quindi la classe con l'iniezione di campi non sarà compatibile con esse.

Il secondo problema è prestazioni . Una chiamata al costruttore (diretta o per riflessione) è sempre più veloce di un mucchio di assegnazioni di campi di riflessione. I framework di iniezione delle dipendenze devono utilizzare l'analisi della riflessione per creare un albero delle dipendenze e creare costruttori di riflessione. Questo processo causa un ulteriore impatto sulle prestazioni.

Le prestazioni influiscono sulla manutenibilità . Se ciascuna suite di test deve essere eseguita in una sorta di contenitore per l'iniezione delle dipendenze, una serie di test di poche migliaia di unità può durare decine di minuti. A seconda delle dimensioni di un codice base, questo potrebbe essere un problema.

Tutto questo solleva molte nuove domande:

  1. Qual è la probabilità di utilizzare parti del codice su un'altra piattaforma?
  2. Quanti test di unità verranno scritti?
  3. Quanto velocemente devo preparare ogni nuova versione?
  4. ...

Generalmente, più grande e importante è un progetto, più significativi sono questi fattori. Inoltre, per quanto riguarda la qualità, vorremmo in generale mantenere il codice elevato in termini di compatibilità, testabilità e manutenibilità. Dal punto di vista filosofico, l'iniezione sul campo rompe incapsulamento , che è uno dei quattro fondamenti di object-oriented programming , che è il paradigma principale di Java.

Molti, molti argomenti contro l'iniezione sul campo.

    
risposta data 24.10.2015 - 06:39
fonte
18

L'iniezione sul campo ottiene un preciso "No" da parte mia.

Come Robert Harvey, è un po 'troppo automagico per i miei gusti. Preferisco il codice esplicito oltre implicito e tollero l'indirezione solo se / quando fornisce vantaggi evidenti in quanto rende il codice più difficile da comprendere e ragionare.

Come Maciej Chałapuk, non mi piace avere bisogno di riflessioni o di un framework DI / IoC per istanziare una classe. È come guidare a Roma per arrivare a Parigi da Amsterdam.

Ti ricordo che amo DI e tutti i vantaggi che porta. Non sono sicuro di aver bisogno dei framework per istanziare una classe. Soprattutto non l'effetto che i contenitori IoC o altri framework DI possono avere sul codice di test.

Mi piace che i miei test siano molto semplici. Non mi piace dover passare attraverso l'indiretto di configurare un contenitore IoC o un altro framework DI per poi impostare la mia classe in prova. Sembra imbarazzante.

E rende i miei test dipendenti dallo stato globale del framework. Ciò significa che non posso più eseguire due test in parallelo che richiedono un'istanza della classe X da configurare con dipendenze diverse.

Almeno con l'iniezione di costruttore e setter, hai la possibilità di non utilizzare i framework e / oi reflection nei tuoi test.

    
risposta data 25.10.2015 - 12:23
fonte
4

Bene, questa sembra una discussione polemica. La prima cosa che deve essere affrontata è che l'iniezione sul campo è diversa dall'iniezione del setter . Lavoro già con alcune persone che pensano che l'iniezione Field and Setter sia la stessa cosa.

Quindi, per essere chiari:

Iniezione sul campo:

@Autowired
private SomeService someService;

Iniezione settetta:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

Ma, per me, condividono le stesse preoccupazioni. E, il mio preferito, l'iniezione del costruttore:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

Ho i seguenti motivi per credere che l'iniezione del costruttore sia migliore dell'iniezione setter / field (citerò alcuni link Spring per dimostrare il mio punto):

  1. Impedisci dipendenze circolari : Spring è in grado di rilevare le dipendenze circolari tra i bean, come spiegato da @Tomasz. Vedi anche Spring Team che dice che.
  2. Le dipendenze della classe sono ovvi : le dipendenze della classe sono evidenti nella funzione di costruzione ma nascosti con l'inserimento campo / setter. Sento le mie lezioni come una "scatola nera" con iniezione di campo / setter. E c'è (secondo la mia esperienza) una tendenza agli sviluppatori a non preoccuparsi della classe con molte dipendenze, che è ovvio quando si tenta di prendere in giro la classe usando l'iniezione del costruttore (si veda l'elemento successivo). È probabile che un problema che disturba gli sviluppatori sia risolto. Inoltre, una classe con troppe dipendenze probabilmente viola il Single Responsibility Principle (SRP).
  3. Facile e affidabile da simulare : rispetto a Field injection , è più facile prendere in giro un test unitario, perché non hai bisogno di alcuna tecnica di simulazione del riflesso o del riflesso per raggiungere il bean privato dichiarato all'interno della classe e non sarai imbrogliato da esso . Basta istanziare la classe e passare i fagioli derisori. Semplice da capire e facile.
  4. Gli strumenti di qualità del codice possono aiutarti : strumenti come Sonar possono avvisarti che la classe diventa troppo complessa, perché una classe con molti parametri è probabilmente una classe troppo complessa e necessita di un refactoring. Questo strumento non è in grado di identificare lo stesso problema quando la classe utilizza l'iniezione Field / Setter.
  5. Codice migliore e indipendente : con meno% di diffusione di@AutoWired tra il tuo codice, il tuo codice è meno dipendente dal framework. So che la possibilità di cambiare semplicemente il framework DI in un progetto non lo giustifica (chi lo fa, giusto?). Ma questo mostra, per me, un codice migliore (ho imparato questo in un modo difficile con EJB e ricerche). E con Spring puoi anche rimuovere l'annotazione @AutoWired usando il constructor injection.
  6. Più annotazioni non è sempre la risposta giusta : Per alcuni reasons , cerco davvero di utilizzare le annotazioni solo quando sono veramente utili.
  7. Puoi rendere immutabili le iniezioni . L'immutabilità è una funzionalità molto apprezzata .

Se stai cercando ulteriori informazioni, ti consiglio questo vecchio (ma ancora rilevante) articolo di Spring Blog che ci dice perché usano così tanto setter injection e il consiglio di usare l'iniezione del costruttore:

setter injection is used a lot more often than you would expect, is the fact that frameworks like Spring in general, are much more suited to be configured by setter injection than by constructor injection

E questa nota sul perché ritengono che il costruttore sia più adatto al codice dell'applicazione:

I think constructor injection is much more usable for application code than it is for framework code. In application code, you inherently have a lesser need for optional values that you need to configure

Considerazioni finali

Se non sei veramente sicuro del vantaggio del costruttore, forse l'idea di combinare setter (non campo) con l'iniezione del costruttore è un'opzione, come spiegato dal Stratega di primavera :

Since you can mix both, Constructor- and Setter-based DI, it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies

Ma ricorda che l'iniezione sul campo è, probabilmente, quella da evitare tra le tre.

    
risposta data 19.02.2018 - 19:26
fonte
-5

È probabile che la dipendenza cambi durante la vita della classe? In tal caso, utilizzare l'iniezione campo / proprietà. Altrimenti usa l'iniezione del costruttore.

    
risposta data 23.10.2015 - 21:15
fonte

Leggi altre domande sui tag