Quali sono i vantaggi dell'iniezione di dipendenza nei casi in cui quasi tutti hanno bisogno di accedere a una struttura dati comune?

20

Ci sono molti motivi per cui i globali sono malvagi in OOP .

Se il numero o le dimensioni degli oggetti che richiedono la condivisione sono troppo grandi per essere passati in modo efficiente nei parametri di funzione, in genere tutti raccomandano Iniezione di dipendenza invece di un oggetto globale.

Tuttavia, nel caso in cui quasi tutti debbano conoscere una determinata struttura di dati, perché la Dependency Injection è migliore di un oggetto globale?

Esempio (uno semplificato, per mostrare il punto in generale, senza scavare troppo in profondità in un'applicazione specifica)

Esiste un numero di veicoli virtuali che hanno un numero enorme di proprietà e stati, da tipo, nome, colore, velocità, posizione, ecc. Un numero di utenti può controllarli a distanza e un numero enorme di eventi ( sia avviato dall'utente che automatico) può modificare molti dei loro stati o proprietà.

La soluzione ingenua sarebbe semplicemente creare un contenitore globale, come

vector<Vehicle> vehicles;

a cui è possibile accedere da qualsiasi luogo.

Più la soluzione OOP-friendly sarebbe quella di avere il contenitore membro della classe che gestisce il ciclo degli eventi principale, e di essere istanziato nel suo costruttore. Ogni classe che ne ha bisogno, ed è membro del thread principale, avrà accesso al contenitore tramite un puntatore nel suo costruttore. Ad esempio, se un messaggio esterno arriva tramite una connessione di rete, una classe (una per ogni connessione) che gestisce l'analisi prenderà il sopravvento e il parser avrà accesso al contenitore tramite un puntatore o un riferimento. Ora se il messaggio analizzato risulta in una modifica di un elemento del contenitore o richiede alcuni dati da esso per eseguire un'azione, può essere gestito senza la necessità di sballottare migliaia di variabili attraverso segnali e slot (o peggio, memorizzandoli nel parser per essere poi recuperati da colui che ha chiamato il parser). Naturalmente, tutte le classi che ricevono l'accesso al contenitore tramite l'iniezione di dipendenza, fanno parte dello stesso thread. Thread diversi non accedono direttamente a questo, ma fanno il loro lavoro e poi inviano segnali al thread principale, e gli slot nel thread principale aggiorneranno il contenitore.

Tuttavia, se la maggior parte delle classi avrà accesso al contenitore, cosa lo rende davvero diverso da un globale? Se così tante classi hanno bisogno dei dati nel contenitore, non è il "modo di iniezione delle dipendenze" solo un mondo mascherato?

Una risposta sarebbe la sicurezza del thread: anche se mi preoccupo di non abusare del contenitore globale, forse un altro sviluppatore in futuro, sotto la pressione di una scadenza ravvicinata, utilizzerà comunque il contenitore globale in un thread diverso, senza prendere cura di tutti i casi di collisione. Tuttavia, anche nel caso di un'iniezione di dipendenza, si potrebbe dare un puntatore a qualcuno che sta correndo su un altro thread, portando agli stessi problemi.

    
posta vsz 22.09.2015 - 15:22
fonte

5 risposte

37

in the case where almost everyone needs to know about a certain data structure, why is Dependency Injection any better than a global object?

L'iniezione di dipendenza è la cosa migliore dopo il pane a fette , mentre gli oggetti globali sono stati riconosciuti per decenni come la fonte di ogni male , quindi questa è una domanda piuttosto interessante .

Il punto di dipendenza da dipendenza è non semplicemente per garantire che ogni attore che ha bisogno di qualche risorsa possa averlo, perché ovviamente, se rendi globali tutte le risorse, allora ogni attore avrà accesso a ogni risorsa , problema risolto, giusto?

Il punto di iniezione della dipendenza è:

  1. Permettere agli attori di accedere alle risorse in base alle necessità e
  2. Per avere il controllo su a quale istanza di una risorsa si accede da un determinato attore.

Il fatto che nella tua configurazione particolare tutti gli attori abbiano bisogno di accedere alla stessa istanza di risorsa è irrilevante. Fidati di me, un giorno avrai la necessità di riconfigurare le cose in modo che gli attori abbiano accesso a diverse istanze della risorsa, e poi ti renderai conto che ti sei dipinto in un angolo. Alcune risposte hanno già indicato una tale configurazione: testing .

Un altro esempio: supponi di aver diviso la tua applicazione in client-server. Tutti gli attori sul client utilizzano lo stesso set di risorse centrali sul client e tutti gli attori sul server utilizzano lo stesso set di risorse centrali sul server. Supponiamo ora, un giorno, di decidere di creare una versione "standalone" dell'applicazione client-server, in cui sia il client che il server siano impacchettati in un unico file eseguibile e in esecuzione nella stessa macchina virtuale. (O ambiente di runtime, a seconda della lingua prescelta.)

Se si utilizza l'iniezione delle dipendenze, è possibile assicurarsi che a tutti gli attori del client vengano fornite le istanze delle risorse client con cui lavorare, mentre tutti gli attori del server ricevono le istanze delle risorse del server.

Se non si utilizza l'iniezione delle dipendenze, si è completamente sfortunati, in quanto solo una istanza globale di ciascuna risorsa può esistere in una macchina virtuale.

Quindi, devi considerare: tutti gli attori hanno davvero bisogno di accedere a quella risorsa? davvero?

È possibile che tu abbia commesso l'errore di trasformare quella risorsa in un oggetto divino, (quindi, ovviamente, tutti hanno bisogno di accedervi) o forse stai sovrastimando il numero di attori nel tuo progetto che in realtà bisogno di l'accesso a quella risorsa.

Con i globali, ogni singola riga di codice sorgente dell'intera applicazione ha accesso a ogni singola risorsa globale. Con l'iniezione di dipendenza, ogni istanza di risorsa è visibile solo agli attori che ne hanno effettivamente bisogno. Se i due sono uguali, (gli attori che necessitano di una particolare risorsa comprendono il 100% delle linee del codice sorgente nel tuo progetto), allora devi aver commesso un errore nel tuo progetto. Quindi,

  • Riforma quella grande e grande risorsa divina in risorse secondarie più piccole, quindi gli attori diversi hanno bisogno di accedere a parti diverse di esso, ma raramente un attore ha bisogno di tutti i suoi pezzi, o

  • Riforma i tuoi attori a loro volta accettano come parametri solo i sottoinsiemi del problema su cui devono lavorare, quindi non devono consultare continuamente grandi risorse centrali enormi.

risposta data 22.09.2015 - 16:01
fonte
39

There are plenty of reasons why non-mutable globals are evil in OOP.

Questa è una richiesta discutibile. Il link che utilizzi come prova si riferisce alle stato - modificabili globali. Sono decisamente cattivi. Leggere solo i globals sono solo costanti. Le costanti sono relativamente equilibrate. Voglio dire, non inietterai il valore di pi in tutte le tue classi, vero?

If the number or size of the objects needing sharing is too large to be efficiently passed around in function parameters, usually everyone recommends Dependency Injection instead of a global object.

No, non lo fanno.

Se le tue funzioni / classi hanno troppe dipendenze, la gente consiglia di fermarsi e dare un'occhiata al motivo per cui il tuo design è così brutto. Avere una barca di dipendenze è un segno che la tua classe / funzione sta probabilmente facendo troppe cose e / o non hai un'astrazione sufficiente.

There is a number of virtual vehicles which have a huge number of properties and states, from type, name, color, to speed, position, etc. A number of users can remote control them, and a huge number of events (both user-initiated and automatic) can change a lot of their states or properties.

Questo è un incubo di design. Hai un sacco di cose che possono essere usate / abusate senza alcun tipo di convalida, nessun tipo di limiti di concorrenza - si accoppiano completamente.

However, if the majority of classes will get access to the container, what makes it really different from a global? If so many classes need the data in the container, isn't the "dependency injection way" just a disguised global?

Sì, l'accoppiamento con i dati comuni ovunque non è troppo diverso da un globale e, come tale, è ancora negativo.

Il vantaggio è che stai disaccoppiando i consumatori dalla variabile globale stessa. Questo ti fornisce alcuni flessibilità nel modo in cui l'istanza viene creata. Inoltre non ti ti costringe ad avere solo un'istanza. Potresti farne di diversi da trasmettere ai tuoi diversi consumatori. È inoltre possibile creare diverse implementazioni dell'interfaccia per consumatore se necessario.

E a causa di cambiamenti inevitabili con i mutevoli requisiti del software, un tale livello base di flessibilità è vitale .

    
risposta data 22.09.2015 - 15:57
fonte
16

Ci sono tre ragioni principali che dovresti prendere in considerazione.

  1. Leggibilità. Se ogni unità di codice ha tutto ciò che deve funzionare su iniettato o passato come parametro, è facile guardare il codice e vedere immediatamente cosa sta facendo. Questo ti dà una località di funzione, che ti permette anche di separare meglio le preoccupazioni e ti costringe anche a pensare a ...
  2. Modularità. Perché il parser dovrebbe conoscere l'intero elenco di veicoli e tutte le loro proprietà? Probabilmente no. Forse ha bisogno di interrogare se esiste un id del veicolo o se il veicolo X ha la proprietà Y. Grande, questo significa che puoi scrivere un servizio che lo fa e iniettare solo quel servizio nel tuo parser. All'improvviso si finisce con un design che ha molto più senso, con ogni bit di codice che tratta solo i dati rilevanti per esso. Il che ci porta a ...
  3. Testability. Per un tipico test di unità, si desidera impostare il proprio ambiente per diversi scenari e l'iniezione rende questa implementazione molto semplice. Di nuovo, tornando all'esempio del parser, vuoi veramente creare manualmente un intero elenco di veicoli a tutti gli effetti per ogni caso di test che scrivi per il tuo parser? O dovresti semplicemente creare un'implementazione finta del suddetto oggetto di servizio, restituendo un numero variabile di id? So quale scegliere.

Quindi riassumendo: DI non è un obiettivo, è solo uno strumento che consente di raggiungere un obiettivo. Se la usi impropriamente (come sembra che tu faccia nel tuo esempio), non sarai più vicino all'obiettivo, non ci sarà alcuna proprietà magica di DI che possa rendere un cattivo design buono.

    
risposta data 22.09.2015 - 15:27
fonte
3

Si tratta davvero di testabilità - normalmente un oggetto globale è molto manutenibile e di facile lettura / comprensione (una volta che lo sai lì, ovviamente) ma è un dispositivo permanente e quando si arriva a dividere le lezioni in test isolati, si trova che il tuo oggetto globale è bloccato dicendo "che dire di me" e quindi i tuoi test isolati richiedono che l'oggetto globale sia incluso in essi. Non aiuta che la maggior parte dei framework di test non supportino facilmente questo scenario.

Quindi sì, è ancora globale. La ragione fondamentale per averla non è cambiata, solo il modo in cui è accessibile.

Per quanto riguarda l'inizializzazione, questo è ancora un problema - devi ancora impostare la configurazione in modo che i test possano essere eseguiti, quindi non stai ottenendo nulla lì. Suppongo che potresti dividere un grande oggetto dipendente in molti più piccoli e passare quello appropriato alle classi che ne hanno bisogno, ma probabilmente otterrai ancora più complessità per superare qualsiasi vantaggio.

Indipendentemente da ciò, è un esempio della "coda che scuote il cane", la necessità di testare sta guidando il modo in cui il codebase è stato architettato (problemi simili si presentano con elementi come le classi statiche, ad esempio l'attuale datetime).

Personalmente preferisco mantenere tutti i miei dati globali in un oggetto globale (o diversi) ma fornire accessor per ottenere i bit di cui le mie classi hanno bisogno. Quindi posso prendere in giro quelli per restituire i dati che voglio quando si tratta di test senza la complessità aggiuntiva di DI.

    
risposta data 22.09.2015 - 15:34
fonte
3

Oltre alle risposte che hai già,

There is a number of virtual vehicles which have a huge number of properties and states, from type, name, color, to speed, position, etc. A number of users can remote control them, and a huge number of events (both user-initiated and automatic) can change a lot of their states or properties.

Una delle caratteristiche della programmazione OOP è di conservare solo le proprietà di cui hai veramente bisogno per un oggetto specifico,

ORA SE il tuo oggetto ha troppe proprietà - dovresti suddividere ulteriormente il tuo oggetto in sotto-oggetti.

Modifica

Già menzionato perché gli oggetti globali sono cattivi, vorrei aggiungere un esempio ad esso.

È necessario eseguire il test dell'unità sul codice della GUI di un modulo di Windows, se si utilizza il global, sarà necessario creare tutto il pulsante e ad esempio per creare un caso di test corretto ...

    
risposta data 22.09.2015 - 15:32
fonte