C'è un vecchio, vecchio dibattito in corso sul modo migliore per fare l'iniezione di dipendenza.
Il taglio originale della molla ha creato un'istanza di un oggetto semplice, quindi ha iniettato dipendenze tramite i metodi setter.
Ma poi una grande contingenza di persone ha insistito sul fatto che l'iniezione di dipendenze attraverso i parametri del costruttore fosse il modo corretto di farlo.
Poi, ultimamente, quando l'uso della riflessione è diventato più comune, l'impostazione dei valori dei membri privati direttamente, senza setter o argomenti del costruttore, è diventata la rabbia.
Quindi il tuo primo costruttore è coerente con il secondo approccio all'iniezione di dipendenza. Ti permette di fare cose carine come iniettare beffe per i test.
Ma il costruttore senza argomenti ha questo problema. Poiché sta creando un'istanza delle classi di implementazione per PaypalCreditCardProcessor
e DatabaseTransactionLog
, crea una dipendenza dura, in fase di compilazione, da PayPal e dal database. Si assume la responsabilità di costruire e configurare correttamente l'intero albero delle dipendenze.
Immagina che il processore PayPay sia un sottosistema davvero complicato e integra molte librerie di supporto. Creando una dipendenza in fase di compilazione su quella classe di implementazione, si crea un collegamento infrangibile all'intero albero delle dipendenze. La complessità del grafico a oggetti è appena aumentata di un ordine di grandezza, forse due.
Molti di questi elementi nell'albero delle dipendenze saranno trasparenti, ma anche molti di essi dovranno essere istanziati. Le probabilità sono che non sarai in grado di creare un'istanza a PaypalCreditCardProcessor
.
Oltre all'istanza, ciascuno degli oggetti richiederà proprietà applicate dalla configurazione.
Se si ha solo una dipendenza sull'interfaccia e si consente a una fabbrica esterna di costruire e iniettare la dipendenza, si interrompe l'intero albero delle dipendenze di PayPal e la complessità del codice si interrompe sull'interfaccia.
Ci sono altri vantaggi, come la specificazione delle classi di implementazione nella configurazione (cioè in fase di esecuzione anziché in fase di compilazione), o avere una specifica di dipendenza più dinamica che varia, diciamo, dall'ambiente (test, integrazione, produzione).
Ad esempio, supponiamo che PayPal Processor avesse 3 oggetti dipendenti e ognuna di queste dipendenze ne avesse altre due. E tutti quegli oggetti devono estrarre le proprietà dalla configurazione. Il codice così com'è dovrebbe assumersi la responsabilità di costruire tutto ciò, impostare le proprietà dalla configurazione, ecc. Ecc. - tutte le preoccupazioni di cui si occuperà il DI framework.
All'inizio potrebbe non sembrare ovvio da cosa ti stai proteggendo utilizzando un framework DI, ma si somma e diventa dolorosamente ovvio nel tempo. (lol parlo per esperienza di aver provato a farlo nel modo più duro)
...
In pratica, anche per un programma davvero minuscolo, trovo che finisco per scrivere in uno stile DI, e suddivido le classi in coppie di implementazione / fabbrica. Cioè, se non sto usando un framework DI come Spring, metto insieme alcune semplici classi di fabbrica.
Ciò fornisce la separazione delle preoccupazioni in modo che la mia classe possa fare solo le sue cose, e la classe di fabbrica si assume la responsabilità di costruire e configurare cose.
Non è un approccio richiesto, ma FWIW
...
Più in generale, il modello DI / interfaccia riduce la complessità del codice facendo due cose:
Inoltre, poiché l'istanziazione e la configurazione degli oggetti sono un compito piuttosto familiare, il framework DI può raggiungere molte economie di scala attraverso la notazione standardizzata e l'uso di trucchi come la riflessione. Spargere quelle stesse preoccupazioni intorno alle lezioni finisce per aggiungere molto più disordine di quanto si possa pensare.