design-patterns cos'è inversion - Quali sono i lati negativi dell'utilizzo di Dependency Injection?





9 Answers

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

Ogni modello o altro costrutto applicato apporta complessità. L'astrazione e l'indiretto sparpagliano le informazioni, a volte spostando il dettaglio irrilevante, ma a volte rendono più difficile capire esattamente cosa sta succedendo. Ogni regola che applichi porta all'inflettibilità, escludendo le opzioni che potrebbero essere solo l'approccio migliore.

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

Link rilevanti

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 i dati vengono iniettati tramite il passaggio del parametro.

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

Prendiamo questa semplice funzione tradizionale - la sintassi del C ++ non è significativa qui, ma devo scriverlo 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;
}

Com'è più inflessibile dell'originale? Bene, cosa succede se decido che l'output dovrebbe essere unicode. Probabilmente voglio passare da std :: cout a std :: wcout. Ma questo significa che le mie stringhe devono essere di wchar_t, non di char. O ogni chiamante deve essere cambiato, 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 mantenuto 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 "dipendenze" da affrontare. Ad esempio, se le risoluzioni dello schermo diventano veramente enormi, forse avremo bisogno di valori di coordinate a 64 bit e un'altra versione di CreateWindowEx. E sì, c'è già una versione precedente ancora in giro, che presumibilmente viene mappata alla nuova versione dietro le quinte ...

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

Quelle "dipendenze" non sono solo un problema per lo sviluppatore originale: chiunque utilizzi tale interfaccia deve cercare quali sono le dipendenze, come sono specificate e cosa significano, e capire cosa fare per la propria applicazione. È qui che le parole "impostazioni predefinite" possono rendere la vita molto più semplice.

Iniezione di dipendenza orientata agli oggetti non è diverso in linea di principio. Scrivere una classe è un overhead, sia nel testo del codice sorgente che nel tempo dello sviluppatore, e se quella classe è scritta per fornire dipendenze in base ad alcune specifiche degli oggetti dipendenti, allora l'oggetto dipendente è bloccato nel supportare quell'interfaccia, anche se c'è un bisogno per sostituire l'implementazione di quell'oggetto.

Nulla di tutto ciò va letto come affermazione che l'iniezione di dipendenza è cattiva, tutt'altro. Ma qualsiasi buona tecnica può essere applicata in modo eccessivo e nel posto sbagliato. Così 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.

of control c#

Sto provando a introdurre DI come schema qui al lavoro e uno dei nostri principali sviluppatori vorrebbe sapere: quali sono gli svantaggi dell'utilizzo del modello Iniezione delle dipendenze?

Nota Sto cercando qui - se possibile - una lista esauriente, non una discussione soggettiva sull'argomento.

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

Modifica : alcuni grandi discussioni / ranting / dibattiti su /r/programming qui.




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




Ho usato Guice (framework DI DI Java) ampiamente negli ultimi 6 mesi. Mentre nel complesso penso sia fantastico (specialmente 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, ho appena trovato un codice che utilizzava un'annotazione personalizzata per iniettare determinati 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 più alta durante l'apprendimento del progetto. Questo è legato al punto 1. Per capire come funziona un progetto che usa l'iniezione di dipendenza, è necessario comprendere sia il modello di iniezione dipendente che il framework specifico. Quando ho iniziato il mio attuale lavoro ho passato un bel po 'di ore confuse a rompere ciò che Guice stava facendo dietro le quinte.
  • I costruttori diventano grandi. Anche se questo può 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 bandiere. Guice ha inghiottito l'errore in silenzio e uno dei miei flag non è stato inizializzato.
  • Gli errori vengono spinti al tempo di esecuzione. Se si configura il modulo Guice in modo errato (riferimento circolare, associazione errata, ...) la maggior parte degli errori non viene scoperta durante la compilazione. Invece, gli errori vengono esposti quando il programma viene effettivamente eseguito.

Ora che mi sono lamentato. Lasciatemi dire che continuerò a (volentieri) usare Guice nel mio attuale progetto e molto probabilmente il mio prossimo. L'iniezione di dipendenza è un modello grande e incredibilmente potente. Ma sicuramente può essere fonte di confusione e quasi certamente passerai un po 'di tempo a maledire qualsiasi struttura di dipendenza da te scelta.

Inoltre, sono d'accordo con gli altri utenti sul fatto che l'iniezione di dipendenza possa essere sovrautilizzata.




Se si dispone di una soluzione sviluppata in proprio, le dipendenze sono corrette nel costruttore. O forse come parametri di metodo che ancora non è difficile da individuare. Sebbene le dipendenze gestite dal framework, se portate all'estremo, possano iniziare ad apparire come magiche.

Tuttavia, avere troppe dipendenze in troppe classi è un chiaro segnale del fatto che la struttura della classe è rovinata. Quindi, in un modo in cui l'iniezione di dipendenza (gestita in casa o gestita dal framework) può contribuire a far emergere problemi di progettazione abbaglianti che potrebbero altrimenti essere nascosti in agguato nell'oscurità.

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

Supponiamo che tu voglia progettare un campus universitario. È necessario delegare parte del design agli studenti e ai professori, altrimenti l'edificio di fisica non funzionerà bene per le persone fisiche. Nessun architetto sa abbastanza di ciò che la fisica ha bisogno di fare tutto da solo. Ma non puoi delegare il design di ogni stanza ai suoi occupanti, perché allora avrai un mucchio enorme di macerie.

Come si può distribuire la responsabilità del design attraverso tutti i livelli di una grande gerarchia, mantenendo al tempo stesso la coerenza e l'armonia del design complessivo? 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 ai suoi occupanti.




Una cosa che mi fa leggermente contrariare con DI è l'assunzione che tutti gli oggetti iniettati sono economici da istanziare e non producono effetti collaterali -OR- la dipendenza viene usata così frequentemente da superare qualsiasi costo di istanza associato.

Dove questo può essere significativo è quando una dipendenza non è usata frequentemente all'interno di una classe di consumo; come qualcosa come un IExceptionLogHandlerService . Ovviamente, un servizio come questo è invocato (si spera :)) raramente all'interno della classe - presumibilmente solo su eccezioni che devono essere registrate; ancora il modello canonico di iniezione-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, dannato il costo / gli effetti collaterali necessari per ottenerlo. Non è probabile che lo sarebbe, ma cosa succederebbe se la costruzione di questa istanza di dipendenza riguardasse un hit di servizio / database, o la ricerca di file di configurazione o una risorsa bloccata fino a quando non venisse eliminata? Se questo servizio fosse invece costruito come necessario, localizzato in un servizio o generato in fabbrica (tutti con problemi propri), si prenderà il costo di costruzione solo quando necessario.

Ora, è un principio di progettazione software generalmente accettato che la costruzione di un oggetto è economico e non produce effetti collaterali. E mentre questa è una buona idea, non è sempre il caso. Usando l'iniezione tipica del costruttore, tuttavia, fondamentalmente richiede che questo sia il caso. Significa che quando crei un'implementazione di una dipendenza, devi progettarla pensando a 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 problema esatto consentendo il caricamento lazy delle dipendenze iniettate, ad esempio fornendo una classe un'istanza Lazy<IService> come dipendenza. Ciò cambierebbe i costruttori degli oggetti dipendenti e renderebbe ancora più consapevoli dei dettagli di implementazione, come la spesa per la costruzione degli oggetti, il che è probabilmente non desiderabile.




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




Se stai usando DI senza un contenitore IOC, il più grande svantaggio è che vedi rapidamente quante dipendenze il tuo codice ha effettivamente e quanto tutto sia realmente accoppiato. ("Ma pensavo che fosse un buon progetto!") La progressione naturale è quella di spostarsi verso un container CIO che può richiedere un po 'di tempo per apprendere e implementare (non così male come la curva di apprendimento del WPF, ma non è libero o). L'ultimo lato negativo è che alcuni sviluppatori inizieranno a scrivere test unitari onesti e di bontà e ci vorrà del tempo per capirlo. Gli sviluppatori che in passato riuscivano a far girare qualcosa in mezza giornata improvvisamente passerebbero due giorni a cercare 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 gettarli fuori dalla porta / in produzione. Quale sarebbe il tuo business piuttosto? Solo tu puoi rispondere a questo.




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




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




Related