C'è un vecchio, vecchio dibattito in corso sul modo migliore di fare l'iniezione di dipendenza.
-
Il taglio originale di primavera creava un'istanza di un oggetto semplice, quindi le dipendeva dall'iniezione tramite i metodi setter.
-
Ma poi una grande contingenza di persone ha insistito sul fatto che iniettare dipendenze attraverso i parametri del costruttore era il modo corretto per farlo.
-
Quindi, ultimamente, quando l'uso del reflection è diventato più comune, l'impostazione dei valori dei membri privati direttamente, senza setter o argomenti del costruttore, è diventata di dominio.
Quindi il tuo primo costruttore è coerente con il secondo approccio all'iniezione di dipendenza. Ti permette di fare cose carine come inject mocks per il test.
Ma il costruttore senza argomenti ha questo problema. Poiché crea un'istanza delle classi di implementazione per PaypalCreditCardProcessor
e DatabaseTransactionLog
, crea una dipendenza dura e in fase di compilazione su PayPal e il Database. Si assume la responsabilità della creazione e della configurazione dell'intero albero delle dipendenze in modo corretto.
-
Immagina che il processore PayPay sia un sottosistema davvero complicato e che aggiunga anche molte librerie di supporto. Creando una dipendenza in fase di compilazione su tale classe di implementazione, si sta creando un collegamento indissolubile all'intero albero delle dipendenze. La complessità del tuo oggetto grafico è appena aumentata di un ordine di grandezza, forse di due.
-
Molti di questi elementi nell'albero delle dipendenze saranno trasparenti, ma molti di essi dovranno anche essere istanziati. Le probabilità sono che non sarai in grado di creare un'istanza di PaypalCreditCardProcessor
.
-
Oltre all'istanza, ciascuno degli oggetti avrà bisogno di proprietà applicate dalla configurazione.
Se si ha solo una dipendenza dall'interfaccia e si consente a un factory esterno di costruire e iniettare la dipendenza, si taglia l'intero albero delle dipendenze di PayPal e la complessità del codice si interrompe nell'interfaccia.
Ci sono altri vantaggi, come quello di specificare le classi di implementazioni in configurazione (ad esempio in fase di esecuzione piuttosto che in fase di compilazione) o con più specifiche di dipendenza dinamica che variano, ad esempio, per ambiente (test, integrazione, produzione).
Ad esempio, supponiamo che PayPalProcessor abbia 3 oggetti dipendenti e ognuna di queste dipendenze ne abbia altre due. E tutti quegli oggetti devono inserire proprietà dalla configurazione. Il codice così come è, si assumerebbe la responsabilità di costruire tutto questo, impostando le proprietà dalla configurazione, ecc. Ecc. - tutte le preoccupazioni che il framework DI si prenderà cura di noi.
Potrebbe non sembrare ovvio in un primo momento da cosa ti stai proteggendo usando un framework DI, ma si aggiunge e diventa dolorosamente ovvio nel tempo. (lol parlo per l'esperienza di aver provato a farlo nel modo più duro)
...
In pratica, anche per un programma veramente piccolo, trovo che finisco per scrivere in uno stile DI e suddividere le classi in coppie implementazione / factory. Cioè, se non sto usando un framework DI come Spring, mi limito a mettere insieme alcune semplici classi di factory.
Ciò fornisce la separazione delle preoccupazioni in modo che la mia classe possa semplicemente fare ciò che è, e la classe factory si assume la responsabilità di costruire & configurazione delle cose.
Non un approccio obbligatorio, ma FWIW
...
Più in generale, il pattern DI / interface riduce la complessità del codice facendo due cose:
Inoltre, poiché l'istanziazione degli oggetti e la configurazione sono un compito abbastanza familiare, il framework DI può ottenere molte economie di scala attraverso la notazione e l'elaborazione standardizzate. usando trucchi come la riflessione. Scattering quelle stesse preoccupazioni intorno alle classi finisce per aggiungere molto più confusione di quanto si potrebbe pensare.