Quali sono gli svantaggi dell'utilizzo di Dependency Injection? [chiuso]


336

Sto cercando di introdurre DI come modello qui al lavoro e uno dei nostri sviluppatori principali vorrebbe sapere: quali sono gli svantaggi dell'utilizzo del modello di iniezione di dipendenza?

Nota Sto cercando un elenco, se possibile, esaustivo, non una discussione soggettiva sull'argomento.


Chiarimento : sto parlando del modello di Dependency Injection (vedi questo articolo di Martin Fowler), non di un framework specifico, sia basato su XML (come Spring) o basato su codice (come Guice), o "auto-rollato" .


Modifica : qualche grande ulteriore discussione / ranting / dibattito in corso / r / programmazione qui.


Potrebbe essere una buona idea specificare se dovremmo discutere di DI stesso o di un tipo specifico di strumento che lo supporta (basato su XML o meno).
Jesse Millikan,

5
la differenza è che NON cerco risposte che si applichino solo a specifici framework, come "XML sucks". :) Sto cercando le risposte che si applicano al concetto di DI come delineato da Fowler qui: martinfowler.com/articles/injection.html
Epaga

3
Non vedo alcun aspetto negativo di DI in generale (costruttore, setter o metodo). Disaccoppia e semplifica senza spese generali.
Kissaki,

2
@kissaki a parte il setter iniettare roba. Meglio creare oggetti utilizzabili durante la costruzione. Se non mi credi, prova ad aggiungere un altro collaboratore a un oggetto con l'iniezione 'setter', otterrai sicuramente un NPE ....
time4tea,

Risposte:


209

Un paio di punti:

  • DI aumenta la complessità, di solito aumentando il numero di classi poiché le responsabilità sono più separate, il che non è sempre vantaggioso
  • Il tuo codice sarà (in qualche modo) accoppiato al framework di iniezione di dipendenza che usi (o più in generale come decidi di implementare il modello DI)
  • I contenitori DI o gli approcci che eseguono la risoluzione dei tipi generalmente comportano una leggera penalità di runtime (molto trascurabile, ma è lì)

In generale, il vantaggio del disaccoppiamento rende ogni attività più semplice da leggere e comprendere, ma aumenta la complessità dell'orchestrazione delle attività più complesse.


72
- La separazione delle classi riduce la complessità. Molte classi non rendono complessa un'applicazione. - Dovresti avere solo una dipendenza dal tuo framework DI nella radice dell'applicazione.
Robert

91
Siamo umani; la nostra memoria a breve termine è limitata e non è in grado di gestire molti <xxx> contemporaneamente. È lo stesso per le classi come per metodi, funzioni, file o qualsiasi costrutto che usi per sviluppare il tuo programma.
Håvard S

45
@Havard S: Sì, ma 5 classi davvero complesse non sono più semplici di 15 semplici. Il modo per ridurre la complessità è ridurre il working set richiesto dal programmatore che lavora sul codice - classi complesse e altamente interdipendenti non lo realizzano.
Kyoryu,

35
@kyoryu Penso che siamo tutti d'accordo su questo. Tieni presente che la complessità non è la stessa dell'accoppiamento . La riduzione dell'accoppiamento può aumentare la complessità. In realtà non sto dicendo che DI è una cosa negativa perché aumenta il numero di classi, sto evidenziando i potenziali aspetti negativi associati ad essa. :)
Håvard S

96
+1 per "DI aumenta la complessità" Questo è vero in DI e oltre. (Quasi) ogni volta che aumentiamo la flessibilità, aumenteremo la complessità. Si tratta di equilibrio, che inizia con la conoscenza degli aspetti positivi e negativi. Quando la gente dice "non ci sono aspetti negativi", è un indicatore sicuro che non hanno ancora compreso appieno la cosa.
Don Branson,

183

Lo stesso problema di base che si presenta spesso con la programmazione orientata agli oggetti, le regole di stile e praticamente tutto il resto. È possibile - molto comune, in effetti - fare troppa astrazione, aggiungere troppa indiretta e applicare generalmente buone tecniche eccessivamente e nei posti sbagliati.

Ogni modello o altro costrutto applicato porta complessità. L'astrazione e l'indirizzamento indiretto sparpagliano le informazioni, a volte spostando i dettagli irrilevanti di mezzo, ma ugualmente a volte rendendo più difficile capire esattamente cosa sta succedendo. Ogni regola che applichi comporta inflessibilità, escludendo opzioni che potrebbero essere l'approccio migliore.

Il punto è scrivere codice che fa il lavoro ed è robusto, leggibile e gestibile. Sei uno sviluppatore di software, non un costruttore di torri d'avorio.

Link pertinenti

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Probabilmente la forma più semplice di iniezione di dipendenza (non ridere) è un parametro. Il codice dipendente dipende dai dati e tali dati vengono iniettati mediante il passaggio del parametro.

Sì, è sciocco e non affronta il punto di iniezione di dipendenza orientato agli oggetti, ma un programmatore funzionale ti dirà che (se hai funzioni di prima classe) questo è l'unico tipo di iniezione di dipendenza di cui hai bisogno. Il punto qui è fare un esempio banale e mostrare i potenziali problemi.

Prendiamo questa semplice funzione tradizionale: la sintassi C ++ non è significativa qui, ma devo scriverla in qualche modo ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Ho una dipendenza che voglio estrarre e iniettare: il testo "Hello World". Abbastanza facile ...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

In che modo è più flessibile dell'originale? Bene, se decidessi che l'output dovrebbe essere unicode. Probabilmente voglio passare da std :: cout a std :: wcout. Ma ciò significa che le mie stringhe devono essere di wchar_t, non di char. O ogni chiamante deve essere modificato, o (più ragionevolmente), la vecchia implementazione viene sostituita con un adattatore che traduce la stringa e chiama la nuova implementazione.

Questo è il lavoro di manutenzione che non sarebbe necessario se avessimo conservato l'originale.

E se sembra banale, dai un'occhiata a questa funzione del mondo reale dall'API Win32 ...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

Sono 12 le "dipendenze" da affrontare. Ad esempio, se le risoluzioni dello schermo diventano davvero enormi, forse avremo bisogno di valori di coordinate a 64 bit e di un'altra versione di CreateWindowEx. E sì, c'è già una versione precedente ancora in giro, che presumibilmente viene mappata alla versione più recente dietro le quinte ...

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Queste "dipendenze" non sono solo un problema per lo sviluppatore originale: tutti coloro che usano quell'interfaccia devono cercare quali sono le dipendenze, come sono specificate e cosa significano, e capire cosa fare per la loro applicazione. È qui che le parole "impostazioni predefinite sensibili" possono semplificare la vita.

L'iniezione di dipendenza orientata agli oggetti non è diversa in linea di principio. Scrivere una classe è un sovraccarico, sia nel testo del codice sorgente che nel tempo dello sviluppatore, e se quella classe è scritta per fornire dipendenze secondo alcune specifiche degli oggetti dipendenti, allora l'oggetto dipendente è bloccato nel supportare quell'interfaccia, anche se è necessario per sostituire l'implementazione di quell'oggetto.

Nulla di tutto questo dovrebbe essere letto come affermando che l'iniezione di dipendenza è male - tutt'altro. Ma ogni buona tecnica può essere applicata eccessivamente e nel posto sbagliato. Proprio come non tutte le stringhe devono essere estratte e trasformate in un parametro, non tutti i comportamenti di basso livello devono essere estratti da oggetti di alto livello e trasformati in una dipendenza iniettabile.


3
L'iniezione di dipendenza non presenta nessuno di questi inconvenienti. Cambia solo il modo in cui le dipendenze del passaggio passano agli oggetti, il che non aggiunge complessità né inflessibilità. Al contrario.
Kissaki,

24
@Kissaki - sì, cambia il modo in cui passi le tue dipendenze. E devi scrivere il codice per gestire il passaggio delle dipendenze. E prima di farlo, devi capire quali dipendenze passare, definirle in un modo che abbia senso per qualcuno che non ha codificato il codice dipendente, ecc. Puoi evitare l'impatto del runtime (ad es. Usando i parametri delle politiche nei modelli in C ++), ma è ancora codice che devi scrivere e gestire. Se è giustificato, è un prezzo molto basso per una grande ricompensa, ma se fai finta che non ci sia un prezzo da pagare, ciò significa che pagherai quel prezzo quando non c'è guadagno.
Steve314,

6
@Kissaki - per quanto riguarda l'inflessibilità, una volta che hai specificato quali sono le tue dipendenze, sei bloccato in quello - altre persone hanno scritto dipendenze da iniettare secondo quella specifica. Se hai bisogno di una nuova implementazione per il codice dipendente che necessita di dipendenze leggermente diverse - difficile, sei bloccato. È ora di iniziare a scrivere alcuni adattatori per tali dipendenze (e un po 'più di sovraccarico e un altro livello di astrazione).
Steve314,

Non sono sicuro di capire questo esempio, se si considera un sovraccarico. Per produrre letteralmente "Hello World", utilizziamo la firma (). Per generare una stringa di 8 bit, utilizzare la firma (char). Per generare unicode, il sovraccarico (wchar_t). Ovviamente, in questo caso, (wchar_t) è quello che gli altri chiamano dietro le quinte. Non è necessaria molta riscrittura del codice. Mi sto perdendo qualcosa?
ingrediente_15939

2
@ ingrediente_15939 - sì, questo è un esempio di giocattolo semplificato. Se lo facessi davvero, utilizzeresti semplicemente il sovraccarico anziché un adattatore poiché il costo della duplicazione del codice è inferiore al costo dell'adattatore. Ma si noti che la duplicazione del codice è generalmente una cosa negativa e l'uso degli adattatori per evitare la duplicazione del codice è un'altra buona tecnica (che a volte può essere usata troppo e nei posti sbagliati).
Steve314,

77

Ecco la mia reazione iniziale: sostanzialmente gli stessi aspetti negativi di qualsiasi modello.

  • ci vuole tempo per imparare
  • se frainteso può portare a più danni che benefici
  • se portato all'estremo può essere più lavoro di quanto giustificherebbe il beneficio

2
Perché ci vuole tempo per imparare? Ho pensato che rendesse le cose semplici rispetto a ejb.
fastcodejava

8
Sembra ondulato, considerando che non vuoi una discussione soggettiva. Suggerisco di costruire (collettivamente, se necessario) un esempio realistico per l'esame. Costruire un campione senza DI sarebbe un buon punto di partenza. Quindi potremmo sfidarlo a modifiche dei requisiti ed esaminare l'impatto. (Questo è estremamente conveniente per me suggerire, a proposito, mentre sto per andare a letto.)
Jesse Millikan

4
Questo ha davvero bisogno di alcuni esempi per il backup, e dopo aver insegnato a dozzine di persone DI, posso dirti che è davvero un concetto molto semplice da imparare e iniziare a mettere in pratica.
Chillitom

4
Non considero davvero DI un modello. Esso (in combinazione con l'IoC) è davvero più un modello di programmazione - se li segui completamente, il tuo codice sembra molto più simile ad un attore che a un OO pseudo-procedurale "tipico".
Kyoryu,

1
+1 Sto usando Symfony 2, il DI è dappertutto nel codice utente, sta esaurendo il solo utilizzo.
MGP,

45

Il più grande "svantaggio" di Inversion of Control (non proprio DI, ma abbastanza vicino) è che tende a rimuovere il fatto di avere un singolo punto per vedere una panoramica di un algoritmo. Questo è fondamentalmente ciò che accade quando hai il codice disaccoppiato: la capacità di guardare in un posto è un artefatto di accoppiamento stretto.


2
Ma sicuramente quel "rovescio della medaglia" è a causa della natura del problema che stiamo risolvendo, disaccoppiandolo in modo da poter facilmente cambiare l'implementazione significa che non c'è un posto dove guardare, e quale rilevanza è in grado di affrontarlo? L'unico caso che posso pensare riguarda il debug e l'ambiente di debug dovrebbe essere in grado di entrare nell'implementazione.
vickirk,

3
Questo è il motivo per cui la parola "rovescio della medaglia" era tra virgolette :) L'accoppiamento libero e l'incapsulamento forte precludono "un posto per vedere tutto", un po 'per definizione. Vedi i miei commenti sulla risposta di Havard S, se hai la sensazione di essere contro DI / IoC.
Kyoryu,

1
Sono d'accordo (ti ho persino votato). Stavo solo chiarendo il punto.
vickirk,

1
@vickirk: Il rovescio della medaglia immagino che sia difficile da comprendere per un essere umano. Il disaccoppiamento delle cose aumenta la complessità nel senso che è più difficile per gli umani capire e impiegare più tempo a comprenderla appieno.
Bjarke Freund-Hansen,

2
Al contrario, uno dei vantaggi di DI è che puoi guardare il costruttore di una singola classe e capire immediatamente da quali classi dipende (cioè da quali classi ha bisogno per fare il suo lavoro). Questo è molto più difficile per altri codici, in quanto il codice può creare classi volenti o nolenti.
Contango,

42

7
Sono curioso, quando qualcuno chiede wonsides, perché le risposte in uno spirito "non ce ne sono" vengono votate e quelle che contengono alcune informazioni relative alla domanda no?
Gabriel Ščerbák,

12
-1 perché non sto cercando una raccolta di link, sto cercando risposte effettive: che ne dici di riassumere i punti di ogni articolo? Poi avrei votato invece. Si noti che gli articoli stessi sono piuttosto interessanti anche se sembra che i primi due siano in realtà debunking negativi di DI?
Epaga,

4
Mi chiedo se anche la risposta più votata sia stata declassata, perché in realtà non risponde affatto alla domanda imho :) Certo, so cosa hai chiesto e cosa ho risposto, non sto chiedendo voti, sono solo confuso perché il mio la risposta non aiuta mentre apparentemente dice che il rovescio della medaglia è che è troppo bello sta aiutando molto.
Gabriel Ščerbák,

41

Ho usato Guice (Java DI framework) ampiamente negli ultimi 6 mesi. Mentre nel complesso penso che sia fantastico (soprattutto dal punto di vista dei test), ci sono alcuni aspetti negativi. Soprattutto:

  • Il codice può diventare più difficile da capire. L'iniezione di dipendenza può essere utilizzata in modi molto ... creativi ... Ad esempio mi sono appena imbattuto in un codice che utilizzava un'annotazione personalizzata per iniettare un certo IOStreams (ad esempio: @ Server1Stream, @ Server2Stream). Mentre questo funziona, e ammetto che ha una certa eleganza, rende la comprensione delle iniezioni di Guice un prerequisito per comprendere il codice.
  • Curva di apprendimento superiore durante l'apprendimento del progetto. Ciò è correlato al punto 1. Per capire come funziona un progetto che utilizza l'iniezione di dipendenza, è necessario comprendere sia il modello di iniezione di dipendenza sia il quadro specifico. Quando ho iniziato il mio attuale lavoro, ho trascorso alcune ore confuse a discutere di ciò che Guice stava facendo dietro le quinte.
  • I costruttori diventano grandi. Sebbene ciò possa essere in gran parte risolto con un costruttore predefinito o una fabbrica.
  • Gli errori possono essere offuscati. Il mio esempio più recente di questo è stato che ho avuto una collisione su 2 nomi di bandiera. Guice inghiottì l'errore in silenzio e una delle mie bandiere non fu inizializzata.
  • Gli errori vengono inviati al runtime. Se il modulo Guice viene configurato in modo errato (riferimento circolare, rilegatura errata, ...) la maggior parte degli errori non viene rilevata durante la compilazione. Al contrario, gli errori vengono visualizzati quando il programma viene effettivamente eseguito.

Ora che mi sono lamentato. Lasciami dire che continuerò (volentieri) a utilizzare Guice nel mio progetto attuale e molto probabilmente nel prossimo. L'iniezione di dipendenza è un modello eccezionale e incredibilmente potente. Ma sicuramente può essere fonte di confusione e quasi sicuramente trascorrerai del tempo imprecando contro qualunque framework di iniezione di dipendenza tu scelga.

Inoltre, sono d'accordo con altri poster che l'iniezione di dipendenza può essere abusata.


14
Non capisco perché le persone non facciano un grande affare di "Gli errori sono spinti al runtime" per me che è un rompicapo, la tipizzazione statica e gli errori di compilazione sono il dono più grande dato agli sviluppatori, non vorrei li per qualsiasi cosa
Richard Tingle,

2
@RichardTingle, IMO durante l'avvio dell'app I moduli DI verranno inizializzati per primi, quindi qualsiasi configurazione errata nel modulo sarà visibile non appena l'app si avvia anziché dopo alcuni giorni o ore. È anche possibile caricare i moduli in modo incrementale, ma se ci atteniamo allo spirito di DI limitando il caricamento del modulo prima di inizializzare la logica dell'applicazione, possiamo isolare con successo i collegamenti errati all'avvio dell'app. Ma se lo configuriamo come anti localizzatore di servizio, quei cattivi collegamenti saranno sicuramente una sorpresa.
k4vin,

@RichardTingle Capisco che non sarà mai simile alla rete di sicurezza fornita dal compilatore ma DI come strumento utilizzato correttamente come indicato nella casella, quindi quegli errori di runtime sono limitati all'inizializzazione dell'app. Quindi possiamo vedere l'inizializzazione dell'app come una sorta di fase di compilazione per i moduli DI. Nella mia esperienza, la maggior parte delle volte se l'app viene avviata, non ci saranno collegamenti errati o riferimenti errati in essa. PS: sto usando NInject con C #
k4vin il

@RichardTingle - Sono d'accordo con te, ma è un compromesso per ottenere un codice vagamente accoppiato, quindi testabile. E come ha detto k4vin, durante l'inizializzazione si trovano dipendenze mancanti e l'uso di interfacce aiuterà ancora con errori di compilazione.
Andrei Epure,

1
Aggiungerei "Difficile mantenere pulito il codice" nel tuo elenco. Non si sa mai se è possibile eliminare una registrazione prima di provare ampiamente il codice senza di essa.
fernacolo,

24

Il codice senza alcun DI comporta il noto rischio di aggrovigliarsi nel codice Spaghetti : alcuni sintomi sono che le classi e i metodi sono troppo grandi, fanno troppo e non possono essere facilmente modificati, scomposti, refactored o testati.

Il codice con DI usato molto può essere il codice Ravioli in cui ogni piccola classe è come una singola pepita di ravioli: fa una piccola cosa e viene rispettato il principio della singola responsabilità , il che è buono. Ma guardare le lezioni da soli è difficile vedere cosa fa il sistema nel suo insieme, poiché questo dipende da come tutte queste piccole parti si incastrano, cosa difficile da vedere. Sembra solo un grande mucchio di piccole cose.

Evitando la complessità degli spaghetti di grandi quantità di codice accoppiato all'interno di una grande classe, si corre il rischio di un altro tipo di complessità, dove ci sono molte piccole classi semplici e le interazioni tra loro sono complesse.

Non penso che questo sia un rovescio della medaglia fatale - DI è ancora molto utile. Un certo grado di ravioli con piccole classi che fanno solo una cosa è probabilmente buono. Anche in eccesso, non penso che sia male come codice spaghetti. Ma essere consapevoli che può essere fatto troppo lontano è il primo passo per evitarlo. Segui i link per la discussione su come evitarlo.


1
Sì, mi piace quel termine "ravioli code"; Lo esprimo più prolisso quando parlo del rovescio della medaglia di DI. Tuttavia, non riesco a immaginare di sviluppare qualsiasi framework o applicazione reale in Java senza DI.
Howard M. Lewis Ship

13

Se hai una soluzione domestica, le dipendenze sono proprio nella tua faccia nel costruttore. O forse come parametri del metodo che di nuovo non è troppo difficile da individuare. Sebbene le dipendenze gestite dal framework, se portate agli estremi, possono iniziare ad apparire come per magia.

Tuttavia, avere troppe dipendenze in troppe classi è un chiaro segno che la struttura della classe è rovinata. Quindi, in un certo senso, l'iniezione di dipendenze (coltivata in casa o gestita da un framework) può aiutare a far emergere evidenti problemi di progettazione che potrebbero altrimenti essere nascosti in agguato nel buio.


Per illustrare meglio il secondo punto, ecco un estratto di questo articolo ( fonte originale ) che credo sinceramente sia il problema fondamentale nella costruzione di qualsiasi sistema, non solo dei sistemi informatici.

Supponiamo che tu voglia progettare un campus universitario. Devi delegare parte del progetto a studenti e professori, altrimenti l'edificio di fisica non funzionerà bene per le persone di fisica. Nessun architetto sa abbastanza di cosa le persone fisiche hanno bisogno per fare tutto da sole. Ma non puoi delegare il design di ogni stanza ai suoi occupanti, perché in questo modo otterrai un enorme mucchio di macerie.

Come è possibile distribuire la responsabilità della progettazione attraverso tutti i livelli di una grande gerarchia, pur mantenendo la coerenza e l'armonia della progettazione generale? Questo è il problema di progettazione architettonica che Alexander sta cercando di risolvere, ma è anche un problema fondamentale dello sviluppo dei sistemi informatici.

DI risolve questo problema? No . Ma ti aiuta a vedere chiaramente se stai cercando di delegare la responsabilità di progettare ogni stanza per i suoi occupanti.


13

Una cosa che mi fa dimenare un po 'con DI è il presupposto che tutti gli oggetti iniettati sono economici da istanziare e non producono effetti collaterali - OPPURE - la dipendenza viene utilizzata così frequentemente da superare qualsiasi costo di istanza associato.

Dove questo può essere significativo è quando una dipendenza non viene usata frequentemente all'interno di una classe consumante; come qualcosa come un IExceptionLogHandlerService. Ovviamente, un servizio come questo viene invocato (si spera :)) raramente all'interno della classe - presumibilmente solo su eccezioni che devono essere registrate; ma il modello canonico dell'iniezione del costruttore ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... richiede che venga fornita un'istanza "live" di questo servizio, maledetti i costi / effetti collaterali necessari per ottenerlo. Non che probabilmente lo farebbe, ma cosa succede se la costruzione di questa istanza di dipendenza comporta un hit del servizio / database, o configura le ricerche dei file o blocca una risorsa fino allo smaltimento? Se questo servizio fosse invece costruito secondo necessità, localizzato nel servizio o generato in fabbrica (tutti con problemi propri), i costi di costruzione sarebbero sostenuti solo quando necessario.

Ora, è un principio di progettazione software generalmente accettato che costruire un oggetto sia economico e non produca effetti collaterali. E mentre questa è una buona idea, non è sempre il caso. L'uso dell'iniezione tipica del costruttore richiede fondamentalmente che sia così. Significato quando si crea un'implementazione di una dipendenza, è necessario progettarla tenendo presente DI. Forse avresti reso la costruzione di oggetti più costosa per ottenere benefici altrove, ma se questa implementazione verrà iniettata, probabilmente ti costringerà a riconsiderare quel progetto.

A proposito, alcune tecniche possono mitigare questo esatto problema consentendo il caricamento lento delle dipendenze iniettate, ad esempio fornendo una classe Lazy<IService>un'istanza come dipendenza. Ciò cambierebbe i costruttori dei tuoi oggetti dipendenti e renderebbe ancora più consapevoli i dettagli dell'implementazione come la spesa per la costruzione di oggetti, che probabilmente non è neppure desiderabile.


1
Capisco quello che stai dicendo, ma non credo sia giusto dire "quando crei un'implementazione di una dipendenza, devi progettarla pensando a DI" - Penso che un'affermazione più accurata sarebbe "DI funziona meglio se usato con classi implementate con le migliori pratiche relative ai costi di istanza e alla mancanza di effetti collaterali o modalità di guasto durante la costruzione ". Nel peggiore dei casi, è sempre possibile iniettare un'implementazione del proxy lazy che difende l'allocazione dell'oggetto reale fino al primo utilizzo.
Jolly Roger,

1
Qualsiasi moderno contenitore IoC ti permetterà di specificare la durata di un oggetto per un tipo / interfaccia astratta specifica (sempre univoco, singleton, con ambito http, ecc.). Puoi anche fornire un metodo / delegato di fabbrica per istanziarlo pigramente usando Func <T> o Lazy <T>.
Dmitry S.

Spot on. L'istanza delle dipendenze costa inutilmente memoria. Normalmente utilizzo il "caricamento lento" con getter che istanziano una classe solo quando vengono chiamati. È possibile fornire un costruttore predefinito per impostazione predefinita senza parametri nonché costruttori successivi che accettano dipendenze istanziate. Nel primo caso, una classe che implementa IErrorLogger, ad esempio, viene istanziata solo this.errorLogger.WriteError(ex)quando si verifica un errore in un'istruzione try / catch.
John Bonfardeci,

12

L'illusione di aver disaccoppiato il codice semplicemente implementando l'iniezione di dipendenza senza REALMENTE disaccoppiarlo. Penso che sia la cosa più pericolosa di DI.


11

Questo è più di un pignolo. Ma uno degli svantaggi dell'iniezione di dipendenza è che rende un po 'più difficile per gli strumenti di sviluppo ragionare e navigare nel codice.

In particolare, se fai clic tenendo premuto il tasto Ctrl / Comando su un richiamo del metodo nel codice, verrai indirizzato alla dichiarazione del metodo su un'interfaccia anziché all'implementazione concreta.

Questo è davvero un aspetto negativo del codice debolmente accoppiato (codice progettato dall'interfaccia) e si applica anche se non si utilizza l'iniezione di dipendenza (cioè, anche se si usano semplicemente fabbriche). Ma l'avvento dell'iniezione di dipendenza è ciò che ha davvero incoraggiato il codice debolmente accoppiato alle masse, quindi ho pensato di menzionarlo.

Inoltre, i vantaggi del codice liberamente accoppiato superano di gran lunga questo, quindi lo chiamo nitpick. Anche se ho lavorato abbastanza a lungo per sapere che questo è il tipo di respingimento che potresti ottenere se provi a introdurre l'iniezione di dipendenza.

In effetti, mi permetto di indovinare che per ogni "aspetto negativo" che puoi trovare per l'iniezione di dipendenza, troverai molti aspetti positivi che lo superano di gran lunga.


5
Ctrl-shift-B con resharper ti porta all'implementazione
Adrianm

1
Ma poi non avremmo (in un mondo ideale) almeno 2 implementazioni di tutto, vale a dire almeno un'implementazione reale e una finta per test unitari ;-)
vickirk

1
In Eclipse se si tiene premuto Ctrl e si passa con il mouse sul codice di invocazione del metodo, verrà visualizzato un menu per aprire la Dichiarazione o Implementazione.
Sam Hasler,

Se sei alla definizione dell'interfaccia, evidenzia il nome del metodo e premi Ctrl + T per visualizzare la gerarchia dei tipi per quel metodo: vedrai tutti gli implementatori di quel metodo in un albero della gerarchia dei tipi.
Qualidafial,

10

L' iniezione di dipendenza basata sul costruttore (senza l'ausilio di "quadri" magici) è un modo pulito e vantaggioso per strutturare il codice OO. Nelle migliori basi di codice che ho visto, negli anni trascorsi con altri ex colleghi di Martin Fowler, ho iniziato a notare che la maggior parte delle buone lezioni scritte in questo modo finiscono per avere un solo doSomethingmetodo.

Il principale svantaggio, quindi, è che una volta che ti rendi conto che tutto è solo un modo OO di lunga durata per scrivere chiusure come classi al fine di ottenere i vantaggi della programmazione funzionale, la tua motivazione a scrivere codice OO può evaporare rapidamente.


1
Il problema con il costruttore è che dovrai sempre aggiungere altri argomenti del costruttore ...
Miguel Ping

1
Haha. Se lo fai troppo, vedrai presto che il tuo codice è scadente e lo rifattrai. Inoltre, ctrl-f6 non è così difficile.
time4tea,

E ovviamente è anche vero il contrario: è tutto solo un modo funzionale a lungo termine di scrivere lezioni come chiusure al fine di ottenere i benefici della programmazione OO
CurtainDog

Molti anni fa ho costruito un sistema a oggetti usando le chiusure in Perl, quindi ottengo quello che stai dicendo, ma l'incapsulamento dei dati non è un vantaggio unico della programmazione OO, quindi non è chiaro quali siano queste vantaggiose funzionalità di OO che sarebbero comparabili kludgy e lungimirante da ottenere in un linguaggio funzionale.
sanityinc

9

Trovo che l'iniezione del costruttore possa portare a brutti costruttori (e la uso in tutto il mio codice - forse i miei oggetti sono troppo granulari?). Inoltre, a volte con l'iniezione del costruttore finisco con orribili dipendenze circolari (anche se questo è molto raro), quindi potresti trovarti a dover avere una sorta di ciclo di vita dello stato pronto con diversi cicli di iniezione di dipendenza in un sistema più complesso.

Tuttavia, preferisco l'iniezione del costruttore rispetto all'iniezione del setter perché una volta che il mio oggetto è stato costruito, allora so senza dubbio in che stato si trova, se si trova in un ambiente di test unitario o caricato in un contenitore IOC. Il che, in una sorta di rotonda, sta dicendo quello che ritengo sia il principale svantaggio dell'iniezione di setter.

(come sidenote, trovo l'intero argomento piuttosto "religioso", ma il tuo chilometraggio varierà con il livello di zelo tecnico nel tuo team di sviluppo!)


8
Se hai grandi brutti costruttori, forse le tue classi sono troppo grandi e devi solo molte dipendenze?
Robert

4
Questa è certamente una possibilità che sono disposto a intrattenere! ... Purtroppo, non ho una squadra con cui posso fare recensioni tra pari. i violini suonano dolcemente in sottofondo, quindi lo schermo si dissolve in nero ...
James B

1
Come Mark Seeman triste sopra "Può essere pericoloso per la tua carriera" ...
Robert

Non avevo idea che le narrazioni di fondo potessero essere così espressive qui: P
Anurag

@Robert Sto cercando di trovare la citazione, dove l'ha detto sopra?
liang,

8

Se stai usando DI senza un contenitore IOC, il più grande svantaggio è vedere rapidamente quante dipendenze ha il tuo codice e quanto strettamente tutto è realmente. ("Ma ho pensato che fosse un buon design!") La naturale progressione è quella di spostarsi verso un contenitore IOC che può richiedere un po 'di tempo per apprendere e implementare (non è così male come la curva di apprendimento del WPF, ma non è gratuito o). L'aspetto negativo è che alcuni sviluppatori inizieranno a scrivere onestamente ai test unitari di bontà e ci vorrà del tempo per capirlo. Gli sviluppatori che in precedenza potevano fare qualcosa in mezza giornata passeranno improvvisamente due giorni cercando di capire come deridere tutte le loro dipendenze.

Simile alla risposta di Mark Seemann, la linea di fondo è che passi il tempo a diventare uno sviluppatore migliore piuttosto che hackerare pezzi di codice insieme e lanciarli fuori dalla porta / in produzione. Quale sarebbe la tua azienda preferirebbe? Solo tu puoi rispondere a questo.


Buon punto. Che è scarsa qualità / consegna veloce vs buona qualità / consegna lenta. Il rovescio della medaglia? DI non è magico, aspettarsi che sia il rovescio della medaglia.
liang

5

DI è una tecnica o un modello e non è correlato a nessun framework. È possibile collegare manualmente le dipendenze. DI ti aiuta con SR (responsabilità singola) e SoC (separazione delle preoccupazioni). DI porta a un design migliore. Dal mio punto di vista ed esperienza non ci sono aspetti negativi . Come con qualsiasi altro modello, puoi sbagliarlo o utilizzarlo in modo improprio (ma cosa succede nel caso di DI piuttosto difficile).

Se si introduce DI come principio in un'applicazione legacy, usando un framework - l'errore più grande che si può fare è abusarne come Service Locator. DI + Framework è fantastico e ha migliorato le cose ovunque l'ho visto! Dal punto di vista organizzativo, ci sono i problemi comuni con ogni nuovo processo, tecnica, modello, ...:

  • Devi formare la tua squadra
  • Devi cambiare la tua applicazione (che include i rischi)

In generale devi investire tempo e denaro , a parte questo, non ci sono aspetti negativi, davvero!


3

Leggibilità del codice. Non sarai in grado di capire facilmente il flusso di codice poiché le dipendenze sono nascoste nei file XML.


9
Non è necessario utilizzare i file di configurazione XML per l'iniezione delle dipendenze: scegliere un contenitore DI che supporti la configurazione nel codice.
Håvard S

8
L'iniezione di dipendenza non implica necessariamente file XML. Sembra che questo possa essere ancora il caso in Java, ma in .NET abbiamo abbandonato questo accoppiamento anni fa.
Mark Seemann,

6
O non usare affatto un contenitore DI.
Jesse Millikan,

se sai che il codice utilizza DI, puoi facilmente supporre chi imposta le dipendenze.
Bozho,

In Java, Guice di Google non ha bisogno di file XML per esempio.
Epaga,

2

Due cose:

  • Richiedono un supporto aggiuntivo per gli strumenti per verificare che la configurazione sia valida.

Ad esempio, IntelliJ (edizione commerciale) supporta la verifica della validità di una configurazione Spring e segnalerà errori come violazioni dei tipi nella configurazione. Senza quel tipo di supporto degli strumenti, non è possibile verificare se la configurazione è valida prima di eseguire i test.

Questo è uno dei motivi per cui il modello "a torta" (come è noto alla comunità Scala) è una buona idea: il cablaggio tra i componenti può essere controllato dal controllo del tipo. Non hai questo vantaggio con annotazioni o XML.

  • Rende molto difficile l'analisi statica globale dei programmi.

Frame come Spring o Guice rendono difficile determinare staticamente l'aspetto del grafico a oggetti creato dal container. Sebbene creino un grafico a oggetti all'avvio del contenitore, non forniscono API utili che descrivono il grafico a oggetti che / verrebbe / verrà creato.


1
Questo è un toro assoluto. L'iniezione di dipendenza è un concetto, non richiede un quadro distorto. Tutto ciò che serve è nuovo. Abbandona la molla e tutta quella merda, quindi i tuoi strumenti funzioneranno bene, inoltre sarai in grado di refactificare il tuo codice molto meglio.
time4tea,

Vero, buon punto. Avrei dovuto essere più chiaro che stavo parlando di problemi con i framework DI e non del modello. È possibile che ho perso il chiarimento alla domanda quando ho risposto (supponendo che fosse lì al momento).
Martin Ellis,

Ah. Nel qual caso ritraggo felicemente il mio fastidio. scuse.
time4tea,

2

Sembra che i presunti benefici di un linguaggio tipizzato staticamente diminuiscano in modo significativo quando si utilizzano costantemente tecniche per aggirare la tipizzazione statica. Un grande negozio Java a cui ho appena intervistato stava mappando le loro dipendenze di build con l'analisi del codice statico ... che doveva essere efficace per analizzare tutti i file Spring.


1

Può aumentare i tempi di avvio dell'app perché il contenitore IoC dovrebbe risolvere le dipendenze in modo corretto e talvolta richiede diverse iterazioni.


3
Giusto per dare una cifra, un contenitore DI dovrebbe risolvere più migliaia di dipendenze al secondo. ( codinginstinct.com/2008/05/… ) I contenitori DI consentono l'istanza differita. Le prestazioni (anche in applicazioni enormi) non dovrebbero essere un problema. O almeno il problema delle prestazioni dovrebbe essere risolvibile e non essere un motivo per decidere contro l'IoC e i suoi framework.
Robert
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.