Iniezione delle dipendenze: Iniezione sul campo vs Iniezione del costruttore?


62

So che si tratta di un acceso dibattito e le opinioni tendono a cambiare nel tempo in merito alla migliore pratica di approccio.

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

Tuttavia, di recente sono entrato in un dibattito con l'autore di JMockit - un quadro beffardo - in cui vede l'iniezione di costruttori e 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? Si preferisce l'iniezione di campo?

Dopo essere passato all'iniezione del 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 di DI, quindi mi sento obbligato a rivedere il mio approccio dato che si sente così fortemente contro l'iniezione del costruttore / setter.



2
Se non ti è permesso usare l'iniezione del costruttore o l' iniezione del setter, come dovresti consegnare le tue dipendenze alla classe? Forse è meglio collegarsi al dibattito, a meno che la conversazione con il ragazzo Mockit non sia privata.
Robert Harvey,

2
@DavidArno: in una classe immutabile, lo stato viene impostato una volta ... nel costruttore.
Robert Harvey,

1
@Davkd quello che stai descrivendo è il modello di dominio anemico. Ha certamente dei sostenitori, ma non è più realmente orientato agli oggetti; dove i dati e le funzioni che agiscono su tali dati convivono.
Richard Tingle,

3
@ David Non c'è niente di sbagliato nella programmazione funzionale. Ma java è fondamentalmente progettato per essere orientato agli oggetti e combatterai costantemente e finirai con il modello di dominio anemico se non stai molto attento. Non c'è niente di sbagliato nel codice orientato alla funzione, ma dovrebbe essere usato uno strumento appropriato, che Java non lo è (anche se Lambda ha aggiunto elementi di programmazione basata sulle funzioni)
Richard Tingle,

Risposte:


48

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 @injectableannotazioni, il che significa che lo stato può essere popolato automagicamente da un agente dall'esterno, anche se il mio stato è stato dichiarato privato. "

Capisco le motivazioni per questo. È un tentativo di evitare gran parte della cerimonia inerente alla corretta organizzazione di una classe. Fondamentalmente, quello che si sta dicendo è che "Sono stanco di scrivere tutto questo bollettino, 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à del linguaggio che probabilmente non dovrebbero essere aggirate. Inoltre, perché fermarsi qui? Tradizionalmente, DI ha fatto affidamento sul fatto che ogni classe avesse un'interfaccia complementare. Perché non eliminare anche tutte quelle interfacce con le annotazioni?

Considera l'alternativa (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;
    }
}

So già alcune cose su questa lezione. È una classe immutabile; lo stato può essere impostato solo nel costruttore (riferimenti, in questo caso particolare). E poiché tutto deriva da un'interfaccia, posso scambiare le implementazioni nel costruttore, che è dove entrano in gioco le tue beffe.

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

È scontato che questo sia un gran numero di piatti, ma è così che è stato progettato il linguaggio. Le annotazioni sembrano un trucco sporco per qualcosa che avrebbe dovuto essere incorporato nel linguaggio stesso.


A meno che non abbia letto male il tuo post, in realtà non stai guardando l'esempio correttamente. La mia implementazione di VeracodeServiceè quasi identica a quella che hai scritto (anche se in Java vs C #). In VeracodeServiceImplTestrealtà è una classe di unit test. I @Injectablecampi sono essenzialmente oggetti derisi che vengono inseriti nel contesto. Il @Testedcampo è l'oggetto / classe con il costruttore DI definito. Sono d'accordo con il tuo punto di vista che preferisce l'iniezione del costruttore rispetto all'iniezione sul campo. Tuttavia, come ho già detto, l'autore di JMockit sente il contrario e sto cercando di capire perché
Eric B.

Se guardi il primo post in quella discussione vedrai che ho quasi lo stesso identico defn nella mia classe Repo.
Eric B.

Se stai parlando solo di classi di test, potrebbe non essere importante. Perché, classi di prova.
Robert Harvey,

No, non sto parlando di lezioni di prova. Sto parlando di classi di produzione. Accade semplicemente che l'esempio nel gruppo di discussione sia un test unitario, dal momento che il framework è un framework di derisione / testing. (L'intera discussione ruota attorno se il quadro beffardo dovrebbe supportare l'iniezione del costruttore)
Eric B.

3
+1: Sì, nessuna magia nella versione C #. È una classe standard, ha dipendenze, sono tutte iniettate ad un certo punto, prima che la classe venga usata. La classe può essere utilizzata con un contenitore DI o meno. Nulla nella classe lo dice IOC o framework "Iniezione automatica delle dipendenze".
Binary Worrier,

27

L'argomentazione di una minore boileplate di inizializzazione del test è valida, 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?

L'uso delle iniezioni di campo significa restringere la compatibilità di una classe con gli ambienti di iniezione di dipendenza che istanziano gli oggetti usando la riflessione e supportano queste annotazioni di iniezione particolari. Alcune piattaforme basate sul linguaggio Java non supportano nemmeno la reflection ( GWT ), quindi la classe iniettata sul campo non sarà compatibile con loro.

Il secondo problema è rappresentato dalle prestazioni . Una chiamata del costruttore (diretta o per riflessione) è sempre più veloce di una serie di assegnazioni di campi di riflessione. I framework di iniezione delle dipendenze devono utilizzare l'analisi della riflessione per costruire un albero delle dipendenze e creare costruttori di riflessioni. Questo processo provoca un aumento delle prestazioni.

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

Tutto ciò solleva molte nuove domande:

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

In generale, più un progetto è grande e importante, più questi fattori sono significativi. Inoltre, dal punto di vista della qualità, vorremmo generalmente mantenere il codice elevato in termini di compatibilità, testabilità e manutenibilità. Dal punto di vista filosofico, l'iniezione di campo rompe l' incapsulamento , che è uno dei quattro fondamenti della programmazione orientata agli oggetti , che è il paradigma principale di Java.

Molti, molti argomenti contro l'iniezione sul campo.


3
+1 Hai colpito un nervo. Non mi piace aver bisogno di riflessione o di un framework DI / IoC per creare un'istanza di una classe. È come guidare a Roma per arrivare a Parigi da Amsterdam. Intendiamoci, adoro DI. Non sono sicuro dei quadri.
Marjan Venema,

1
Grandi argomenti. Verbalizza molti dei pensieri che ho avuto, ma sono felice di vedere gli altri sentirsi allo stesso modo. Per quanto incredibile trovassi l'iniezione sul campo, penso di averlo amato così tanto semplicemente perché era "più facile". Trovo che l'iniezione del costruttore sia molto più chiara ora.
Eric B.

19

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

Come Robert Harvey, è un po 'troppo automagico per i miei gusti. Preferisco il codice esplicito rispetto a quello implicito e tollero l'indirizzamento indiretto solo come / quando fornisce chiari vantaggi in quanto rende il codice più difficile da capire e ragionare.

Come Maciej Chałapuk, non mi piace aver bisogno di una riflessione o di un framework DI / IoC per creare un'istanza di una classe. È come guidare a Roma per arrivare a Parigi da Amsterdam.

Intendiamoci, amo DI e tutti i vantaggi che ne derivano. Non sono sicuro di aver bisogno dei framework per creare un'istanza di 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 per la direzione indiretta della creazione di un contenitore IoC o di un altro framework DI per poi metterli alla prova con la mia classe. Sembra solo 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 di classe X da configurare con dipendenze diverse.

Almeno con l'iniezione del costruttore e del setter, hai la possibilità di non usare le strutture e / o la riflessione nei tuoi test.


Se si utilizza, ad esempio, Mockito mockito.org , non è necessario un framework DI / IoC anche quando si utilizza l'iniezione di campo e si possono eseguire test in parallelo e così via.
Hans-Peter Störr,

1
@hstoerr Nice. Tuttavia, ciò deve significare che Mockito sta usando la riflessione (o come si chiama l'equivalente Java di quello) ...
Marjan Venema,

5

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

Quindi, per essere chiari:

Iniezione di campo:

@Autowired
private SomeService someService;

Iniezione setter:

@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 collegamenti a molla per dimostrare il mio punto):

  1. Prevenire le dipendenze circolari : Spring è in grado di rilevare dipendenze circolari tra i fagioli, come spiegato da @Tomasz. Vedi anche lo Spring Team che lo dice .
  2. Le dipendenze della classe sono ovvie : le dipendenze della classe sono evidenti nel costruttore ma nascoste con l'iniezione di campo / setter. Sento le mie lezioni come una "scatola nera" con iniezione di campo / setter. E c'è (secondo la mia esperienza) una tendenza per gli sviluppatori a non preoccuparsi della classe con molte dipendenze, questo è ovvio quando provi a deridere la classe usando l'iniezione del costruttore (vedi il prossimo articolo). È molto probabile che sia risolto un problema che disturba gli sviluppatori. Inoltre, una classe con troppe dipendenze probabilmente viola il Single Responsibility Principle (SRP).
  3. Facile e affidabile da deridere : rispetto all'iniezione di campo , è più facile deridere in un test di unità, perché non è necessaria alcuna tecnica di simulazione o riflessione del contesto per raggiungere il bean privato dichiarato all'interno della classe e non ne rimarrai ingannato . Devi solo istanziare la classe e passare i fagioli derisi. Semplice da capire e facile.
  4. Gli strumenti di qualità del codice possono aiutarti : strumenti come Sonar possono avvisarti che la classe sta diventando troppo complessa, perché una classe con molti parametri è probabilmente una classe troppo complessa e necessita di alcuni refactor. Questo strumento non è in grado di identificare lo stesso problema quando la classe utilizza l'iniezione Field / Setter.
  5. Codice migliore e indipendente : con una minore @AutoWireddiffusione tra il codice, il codice dipende meno 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 (l'ho imparato duramente con EJB e ricerche). E con Spring puoi persino rimuovere l' @AutoWiredannotazione usando l'iniezione del costruttore.
  6. Più annotazioni non è sempre la risposta giusta : per alcuni motivi , cerco davvero di usare le annotazioni solo quando sono davvero utili.
  7. Puoi rendere immutabili le iniezioni . L'immutabilità è una caratteristica molto apprezzata .

Se stai cercando maggiori informazioni, ti consiglio questo vecchio (ma comunque rilevante) articolo del Blog di Primavera che ci spiega perché usano così tanto l'iniezione setter e la raccomandazione di usare l'iniezione del costruttore:

l'iniezione del setter viene utilizzata molto più spesso di quanto ci si aspetterebbe, è il fatto che strutture come Spring in generale, sono molto più adatte per essere configurate dall'iniezione del setter che dall'iniezione del costruttore

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

Penso che l'iniezione del costruttore sia molto più utilizzabile per il codice dell'applicazione che per il codice del framework. Nel codice dell'applicazione, hai intrinsecamente una minore necessità di valori opzionali che devi configurare

Pensieri finali

Se non sei davvero sicuro del vantaggio del costruttore, forse l'idea di mescolare il setter (non il campo) con l'iniezione del costruttore è un'opzione, come spiegato dal team di Spring :

Poiché è possibile combinare DI, sia basati su costruttori che su setter, è una buona regola empirica utilizzare argomenti di costruzione per dipendenze obbligatorie e setter per dipendenze opzionali

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


-5

È probabile che la tua dipendenza cambi durante la durata della lezione? In tal caso, utilizzare l'iniezione di campo / proprietà. Altrimenti usi l'iniezione del costruttore.


2
Questo non spiega davvero nulla. Perché iniettare campi privati ​​quando puoi farlo nel modo corretto?
Robert Harvey,

Dove dice qualcosa sui campi privati? Sto parlando dell'interfaccia pubblica di una classe /
Darren Young,

Non ci sono argomenti sull'iniezione del metodo contro l'iniezione del costruttore nella domanda. L'autore di JMockit considera entrambi cattivi. Guarda il titolo della domanda.
Robert Harvey,

La domanda non dice iniezione sul campo vs iniezione del costruttore?
Darren Young,

1
L'iniezione sul campo è un animale completamente diverso. Stai inserendo valori nei membri privati .
Robert Harvey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.