2013-04-16 9 views
26

Quindi, al momento sto riprogettando una mia app Android per utilizzare Dagger. La mia app è ampia e complicata e di recente mi sono imbattuto nel seguente scenario:Utilizzo di Dagger per l'iniezione delle dipendenze sui costruttori

L'oggetto A richiede un'istanza speciale DebugLogger che è un candidato ideale per l'iniezione. Invece di passare attorno al registratore, posso semplicemente iniettarlo attraverso il costruttore di A. Assomiglia a questo:

class A 
{ 
    private DebugLogger logger; 

    @Inject 
    public A(DebugLogger logger) 
    { 
     this.logger = logger; 
    } 

    // Additional methods of A follow, etc. 
} 

Finora questo ha senso. Tuttavia, A ha bisogno di essere costruita da un'altra classe B. più istanze di un devono essere costruite in modo seguente modo di fare le cose di Dagger, io semplice iniettare un Provider<A> in B:

class B 
{ 
    private Provider<A> aFactory; 

    @Inject 
    public B(Provider<A> aFactory) 
    { 
     this.aFactory = aFactory; 
    } 
} 

Ok, bene finora. Ma aspetta, improvvisamente A ha bisogno di ulteriori input, come un intero chiamato "quantità" che è vitale per la sua costruzione. Ora, il mio di costruzione per una ha bisogno di guardare in questo modo:

@Inject 
public A(DebugLogger logger, int amount) 
{ 
... 
} 

Improvvisamente questo nuovo parametro interferisce con l'iniezione. Inoltre, anche se questo ha funzionato, non ci sarebbe stato modo per me di passare "importo" quando si recupera una nuova istanza dal provider, a meno che non mi sbagli. Ci sono molte cose che potrei fare qui, e la mia domanda è qual è la migliore?

Ho potuto refactoring A aggiungendo un metodo setAmount() che si prevede di essere chiamato dopo il costruttore. Questo è brutto, comunque, perché mi costringe a ritardare la costruzione di A fino a quando "importo" è stato compilato. Se avessi due parametri quali "importo" e "frequenza", allora avrei due setter, il che significherebbe sia complicato il controllo per garantire che la costruzione di a riprende dopo due setter sono chiamati, o avrei dovuto aggiungere ancora un terzo metodo nella miscela, in questo modo:

(Somewhere in B): 

A inst = aFactory.get(); 
inst.setAmount(5); 
inst.setFrequency(7); 
inst.doConstructionThatRequiresAmountAndFrequency(); 

l'altra alternativa è che io non uso costruttore iniezione basata su base e andare con l'iniezione basata sul campo. Ma ora, devo rendere pubblici i miei campi. Questo non mi sta bene, perché ora sono obbligato a rivelare i dati interni delle mie lezioni ad altre classi.

Finora, l'unica soluzione un po 'elegante mi viene in mente è quello di utilizzare l'iniezione di campo-base per i fornitori, in questo modo:

class A 
{ 
    @Inject 
    public Provider<DebugLogger> loggerProvider; 
    private DebugLogger logger; 

    public A(int amount, int frequency) 
    { 
     logger = loggerProvider.get(); 
     // Do fancy things with amount and frequency here 
     ... 
    } 
} 

Ancora oggi, sono incerto sui tempi, da quando ho' Non sono sicuro che Dagger inietterà il provider prima che venga chiamato il costruttore.

C'è un modo migliore? Mi sto perdendo qualcosa su come funziona Dagger?

risposta

49

Quello di cui si parla è noto come iniezione assistita e attualmente non è supportato da Dagger in alcun modo automatico.

È possibile aggirare questo con il modello di fabbrica:

class AFactory { 
    @Inject DebugLogger debuggLogger; 

    public A create(int amount, int frequency) { 
    return new A(debuggLogger, amount); 
    } 
} 

Ora è possibile iniettare questa fabbrica e utilizzarlo per creare istanze di A:

class B { 
    @Inject AFactory aFactory; 

    //... 
} 

e quando è necessario creare un A con il tuo 'importo' e 'frequenza' si utilizza la fabbrica.

A a = aFactory.create(amount, frequency); 

Questo permette di avere Afinal istanze del registratore, quantità, e campi di frequenza, pur utilizzando iniezione di fornire l'istanza logger.

Guice ha un plug-in per l'iniezione assistita che essenzialmente automatizza la creazione di queste fabbriche. Ci have been discussion sulla mailing list di Dagger sul modo appropriato per loro di essere aggiunti ma nulla è stato deciso al momento della stesura.

+0

Grazie per la rapida risposta. Il modello di fabbrica che hai descritto sembra l'approccio migliore. Mi sono anche reso conto che se Dagger avesse supportato l'iniezione in campo privato, avrebbe facilmente permesso ciò che sto cercando di realizzare. Mi chiedo perché non fosse parte del design originale di Dagger? Presumo che Dagger inietti utilizzando la riflessione. Se è così, i campi privati ​​non dovrebbero porre problemi, giusto? – Alex

+0

Il pugnale ricade nel riflesso ma non è il suo principale mezzo di iniezione. Genera codice che imposta direttamente i campi o chiama i tuoi costruttori. Come tale funziona come qualsiasi altro pezzo di codice nella struttura dei sorgenti e non può accedere ai membri 'private'. –

+0

E alcuni responsabili della sicurezza interromperanno la riflessione che si basa sulla modifica dell'accessibilità nelle classi. –

3

Quello che dice il post di Jake è perfettamente vero. Detto questo, noi (alcuni dei Google folk che lavorano con Guice e Dagger) stanno lavorando a una versione alternativa di "injection assistito" o generazione di factory automatiche che dovrebbe essere utilizzabile da Guice o Dagger o stand-alone - cioè, genererà codice sorgente di classe fabbrica per te. Queste classi di fabbrica saranno (se necessario) iniettabili come farebbe qualsiasi classe standard JSR-330. Ma non è ancora stato rilasciato.

In attesa di una soluzione come questa, è consigliabile l'approccio di Jake Wharton.

+0

Sì, no, mi rendo conto che Dagger è ancora nelle sue prime versioni. Mi piace il fatto che alcune di queste altre funzionalità siano sviluppate come plugin per mantenere la dimensione complessiva del file binario (che è, almeno per me, ciò che lo rende un'opzione interessante per lo sviluppo di Android). – Alex

+1

Generazione di generazione automatica menzionata in precedenza: https://github.com/google/auto/tree/master/factory – phazei

3

Stai avendo un problema perché stai mescolando iniettabili e non iniettabili nel tuo costruttore. Le regole generali per l'iniezione che vi farà risparmiare un sacco di angoscia e di mantenere il vostro codice pulito sono:

  1. iniettabili possono richiedere altri iniettabili nel loro costruttore, ma non per newables.

  2. I newables possono chiedere altri newables nel proprio costruttore ma non per iniettabili.

iniettabili sono oggetti di tipo di servizio, cioè oggetti che funzionano come un CreditCardProcessor, MusicPlayer, ecc

newables sono tipo di valore oggetti come CreditCard, Song, ecc

0

post di Jake è fantastico, ma c'è un modo più semplice. Google ha creato la libreria AutoFactory per la creazione automatica di fabbrica in fase di compilazione.

In primo luogo, creare classe A con @AutoFactory annotazione e @Provided di annotazione per l'iniezione di argomenti:

@AutoFactory 
public class A { 

    private DebugLogger logger; 

    public A(@Provided DebugLogger logger, int amount, int frequency) { 
     this.logger = logger; 
    } 
} 

Poi biblioteca crea AFactory classe al momento della compilazione. Quindi è sufficiente iniettare la fabbrica in un costruttore della classe B.

public class B { 

    private final AFactory aFactory; 

    @Inject 
    public B(AFactory aFactory) { 
     this.aFactory = aFactory; 
    } 

    public A createA(int amount, int frequency) { 
     return aFactory.create(amount, frequency); 
    } 
} 
Problemi correlati