Ci sono state diverse domande già postate con domande specifiche sulla dependency injection, come quando usarla e quali framework ci sono per essa. Tuttavia,
**Che cos'è la dependency injection e quando/perché dovrebbe o non dovrebbe essere usata?
La migliore definizione che ho trovato finora è quella di James Shore:
"Dependency Injection" è un termine da 25 dollari termine per un concetto da 5 centesimi. [...] Iniezione di dipendenza significa dare ad un oggetto le sue variabili di istanza. [...].
C'è <a href="http://martinfowler.com/articles/injection.html" un articolo di Martin Fowler che potrebbe rivelarsi utile, anche.
Dependency injection è fondamentalmente fornire gli oggetti di cui un oggetto ha bisogno (le sue dipendenze) invece di farglieli costruire da solo. È una tecnica molto utile per i test, poiché permette alle dipendenze di essere derise o stubbate.
Le dipendenze possono essere iniettate negli oggetti in molti modi (come l'iniezione del costruttore o del setter). Si possono anche usare framework di dependency injection specializzati (ad esempio Spring) per farlo, ma certamente non sono necessari. Non c'è bisogno di questi framework per avere l'iniezione di dipendenze. Istanziare e passare oggetti (dipendenze) esplicitamente è un'iniezione altrettanto buona quanto l'iniezione tramite framework.
Dependency Injection è passare la dipendenza ad altri oggetti o framework (dependency injector).
L'iniezione di dipendenza rende i test più facili. L'iniezione può essere fatta attraverso il costruttore.
SomeClass()
ha il suo costruttore come segue:
-- language-all: lang-csh -->
public SomeClass() {
myObject = Factory.getObject();
}
Problema:
Se myObject
coinvolge compiti complessi come l'accesso al disco o alla rete, è difficile fare test unitari su SomeClass()
. I programmatori devono prendere in giro myObject
e potrebbero intercettare la chiamata al factory.
Soluzione alternativa:
myObject
come argomento al costruttore-- language-all: lang-csh -->
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject
può essere passato direttamente, il che rende i test più facili.
È più difficile isolare i componenti nei test unitari senza l'iniezione delle dipendenze.
Nel 2013, quando ho scritto questa risposta, questo era un tema importante sul Google Testing Blog. Rimane il più grande vantaggio per me, poiché i programmatori non sempre hanno bisogno della flessibilità extra nella loro progettazione run-time (ad esempio, per il localizzatore di servizi o modelli simili). I programmatori hanno spesso bisogno di isolare le classi durante i test.
Dependency Injection è una pratica in cui gli oggetti sono progettati in modo da ricevere istanze degli oggetti da altri pezzi di codice, invece di costruirli internamente. Questo significa che qualsiasi oggetto che implementa l'interfaccia richiesta dall'oggetto può essere sostituito senza cambiare il codice, il che semplifica i test e migliora il disaccoppiamento.
Per esempio, considerate questi clase:
-- language-all: lang-csh -->
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
In questo esempio, l'implementazione di PersonService::addManager
e PersonService::removeManager
avrebbe bisogno di un'istanza di GroupMembershipService
per fare il suo lavoro. Senza Dependency Injection, il modo tradizionale di farlo sarebbe quello di istanziare un nuovo GroupMembershipService
nel costruttore di PersonService
e usare quell'attributo di istanza in entrambe le funzioni. Tuttavia, se il costruttore di GroupMembershipService
ha più cose che richiede, o peggio ancora, ci sono alcuni "setter" di inizializzazione che devono essere chiamati sul GroupMembershipService
, il codice cresce piuttosto rapidamente, e il PersonService
ora dipende non solo dal GroupMembershipService
ma anche da tutto il resto che dipende dal GroupMembershipService
. Inoltre, il collegamento al GroupMembershipService
è hardcoded nel PersonService
, il che significa che non puoi "dummy up" un GroupMembershipService
per scopi di test, o per usare un modello di strategia in diverse parti della tua applicazione.
Con Dependency Injection, invece di istanziare il GroupMembershipService
all'interno del proprio PersonService
, lo si passa al costruttore del PersonService
, oppure si aggiunge una proprietà (getter e setter) per impostare un'istanza locale di esso. Questo significa che il tuo PersonService
non deve più preoccuparsi di come creare un GroupMembershipService
, semplicemente accetta quelli che gli vengono dati e lavora con loro. Questo significa anche che qualsiasi cosa che sia una sottoclasse di GroupMembershipService
, o che implementi l'interfaccia GroupMembershipService
può essere "iniettata" nel PersonService
, e il PersonService
non deve sapere del cambiamento.