Perché dovrei usare l'iniezione di dipendenza?


95

Sto facendo fatica a cercare risorse sul perché dovrei usare l' iniezione di dipendenza . La maggior parte delle risorse che vedo spiega che passa solo un'istanza di un oggetto a un'altra istanza di un oggetto, ma perché? Questo è solo per un'architettura / codice più pulito o influisce sulle prestazioni nel loro insieme?

Perché dovrei fare quanto segue?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Invece di quanto segue?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}

8
Stai introducendo una dipendenza hardcoded per disableateProfile () (che è male). Hai più codice disaccoppiato nel primo, il che rende più facile cambiare e testare.
Aulis Ronkainen,

3
Perché dovresti fare il primo? Stai passando in un'impostazione e quindi ignori il suo valore.
Phil N DeBlanc,

57
Non sono d'accordo con i voti negativi. Mentre l'argomento può essere considerato banale per gli esperti, la domanda ha merito: se si dovrebbe usare l'inversione di dipendenza, allora ci dovrebbe essere una giustificazione per usarla.
Flater,

13
@PhilNDeBlanc: questo codice è chiaramente semplificato e non molto indicativo della logica del mondo reale. Tuttavia, deactivateProfilemi suggerisce che l'impostazione isActivesu false senza preoccuparsi del suo stato precedente sia l'approccio corretto qui. Chiamare il metodo intrinsecamente significa che si intende impostarlo come inattivo, non ottenere lo stato corrente (in) attivo.
Flater,

2
Il tuo codice non è un esempio di iniezione di dipendenza o inversione. È un esempio di parametrizzazione (che spesso è molto meglio di DI).
jpmc26,

Risposte:


104

Il vantaggio è che senza iniezione di dipendenza, la tua classe di profilo

  • deve sapere come creare un oggetto Impostazioni (viola il singolo principio di responsabilità)
  • Crea sempre l'oggetto Impostazioni allo stesso modo (crea un accoppiamento stretto tra i due)

Ma con iniezione di dipendenza

  • La logica per la creazione di oggetti Settings è altrove
  • È facile usare diversi tipi di oggetti Impostazioni

Questo può sembrare (o addirittura essere) irrilevante in questo caso particolare, ma immagina se non stiamo parlando di un oggetto Settings, ma di un oggetto DataStore, che potrebbe avere implementazioni diverse, una che memorizza i dati in file e un'altra che li memorizza in un database. E per i test automatizzati anche tu vuoi un'implementazione fittizia. Ora non vuoi davvero che la classe Profile esegua il hardcode di quello che usa - e ancora più importante, davvero, davvero , non vuoi davvero che la classe Profile sia a conoscenza di percorsi di filesystem, connessioni DB e password, quindi la creazione di oggetti DataStore deve succedere altrove.


23
This may seem (or even be) irrelevant in this particular casePenso che sia molto rilevante, in effetti. Come otterresti le impostazioni? Molti sistemi che ho visto avranno un set predefinito di impostazioni hardcoded e una configurazione pubblica, quindi dovrai caricare entrambi e sovrascrivere alcuni valori con le impostazioni pubbliche. Potresti anche aver bisogno di più fonti di default. Forse potresti anche averne alcuni dal disco, altri dal DB. Quindi, l'intera logica anche per ottenere le impostazioni può, e spesso è, non banale - sicuramente non qualcosa che consuma il codice dovrebbe o dovrebbe interessarsene.
VLAZ,

Potremmo anche menzionare che l'inizializzazione dell'oggetto per un componente non banale, come un servizio Web, renderebbe $setting = new Setting();orribilmente inefficiente. L'istanza di iniezione e oggetto avviene una volta.
vikingsteve,

9
Penso che l'uso di simulazioni per i test dovrebbe avere maggiore enfasi. È probabile che se guardi solo il codice, sarà sempre un oggetto Impostazioni e non cambierà mai, quindi passarlo sembra uno sforzo sprecato. Tuttavia, la prima volta che provi a testare un oggetto Profile da solo senza bisogno anche di un oggetto Settings (usando un oggetto finto come soluzione) la necessità è molto evidente.
JPhi1618,

2
@ JPhi1618 Penso che il problema con l'enfatizzazione di "DI è per unit test" è che porta solo alla domanda "perché ho bisogno di unit test". La risposta può sembrare ovvia per te, e i vantaggi sono sicuramente lì, ma a qualcuno che ha appena iniziato, dicendo "devi fare questa cosa dal suono complicato per fare questa altra cosa dal suono complicato" tende ad essere un po 'di una svolta. Quindi è bene menzionare diversi vantaggi che potrebbero essere più applicabili a ciò che stanno facendo in questo momento.
IMSoP

1
@ user986730 - questo è un ottimo punto, e sottolinea che il vero principio qui è l' inversione delle dipendenze , di cui l'iniezione è solo una tecnica, quindi ad esempio, in C, non puoi davvero iniettare dipendenze usando una libreria IOC, ma puoi invertili al momento della compilazione, includendo diversi file sorgente per i tuoi mock, ecc., che hanno lo stesso effetto.
Stephen Byrne,

64

L'iniezione delle dipendenze semplifica il test del codice.

L'ho imparato di prima mano quando mi è stato assegnato il compito di correggere un bug difficile da catturare nell'integrazione di Magento con PayPal.

Un problema sorgeva quando PayPal informava Magento di un pagamento non riuscito: Magento non registrava correttamente l'errore.

Testare una potenziale correzione "manualmente" sarebbe molto noioso: dovresti in qualche modo attivare una notifica PayPal "non riuscita". Dovresti inviare un e-check, annullarlo e attendere che si verifichi un errore. Ciò significa 3+ giorni per testare una modifica del codice di un carattere!

Fortunatamente, sembra che gli sviluppatori principali di Magento che hanno sviluppato questa funzione abbiano in mente i test e abbiano usato un modello di iniezione di dipendenza per renderlo banale. Questo ci consente di verificare il nostro lavoro con un semplice test case come questo:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

Sono sicuro che il modello DI ha molti altri vantaggi, ma una maggiore testabilità è il più grande vantaggio nella mia mente .

Se sei curioso di sapere la soluzione a questo problema, dai un'occhiata al repository GitHub qui: https://github.com/bubbleupdev/BUCorefix_Paypalstatus


4
L'iniezione di dipendenza semplifica il test del codice rispetto al codice con dipendenze codificate. Eliminare del tutto le dipendenze dalla logica aziendale è ancora meglio.
Formica P,

1
E un modo importante per fare ciò che suggerisce @AntP è tramite la parametrizzazione . La logica per trasformare i risultati che ritornano dal database nell'oggetto utilizzato per compilare un modello di pagina (comunemente noto come "modello") non dovrebbe nemmeno sapere che sta avvenendo un recupero; deve solo ottenere quegli oggetti come input.
jpmc26,

4
@ jpmc26 in effetti - tendo a parlare di core funzionale, shell imperativa , che in realtà è solo un nome di fantasia per passare i dati nel tuo dominio invece di iniettare dipendenze in esso. Il dominio è puro, può essere testato in unità senza simulazioni e poi è semplicemente avvolto in una shell che adatta cose come persistenza, messaggistica, ecc.
Ant P

1
Penso che la sola attenzione alla testabilità sia dannosa per l'adozione di DI. Lo rende poco attraente per le persone che si sentono come se non avessero bisogno di molti test o pensino di avere già i test sotto controllo. Direi che è praticamente impossibile scrivere codice pulito e riutilizzabile senza DI. I test sono molto in basso nell'elenco dei vantaggi ed è deludente vedere questa risposta classificata 1 ° o 2 ° in ogni domanda sui vantaggi di DI.
Carl Leth,

26

Perché (qual è anche il problema)?

Perché dovrei usare l'iniezione di dipendenza?

Il miglior mnemonico che ho trovato per questo è " new is glue ": ogni volta che lo usi newnel tuo codice, quel codice è legato a quella specifica implementazione . Se usi ripetutamente nuovi nei costruttori, creerai una catena di implementazioni specifiche . E poiché non puoi "avere" un'istanza di una classe senza costruirla, non puoi separare quella catena.

Ad esempio, immagina di scrivere un videogioco per auto da corsa. Hai iniziato con una classe Game, che crea a RaceTrack, che crea 8 Cars, che crea ciascuno a Motor. Ora, se vuoi altri 4 Carscon un'accelerazione diversa, dovrai cambiare ogni classe menzionata , tranne forse Game.

Codice più pulito

Questo è solo per un'architettura / codice più pulito

.

Tuttavia, potrebbe benissimo sembrare meno chiaro in questa situazione, perché è più un esempio di come farlo . Il vantaggio effettivo mostra solo quando sono coinvolte più classi ed è più difficile da dimostrare, ma immagina che avresti usato DI nell'esempio precedente. Il codice che crea tutte queste cose potrebbe assomigliare a questo:

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

È ora possibile aggiungere quelle 4 macchine diverse aggiungendo queste righe:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • Nessun cambiamento in RaceTrack, Game, Car, o Motorsono stati necessari - il che significa che possiamo essere sicuri al 100% che non ci possono introdurre nuovi bug lì!
  • Invece di dover saltare tra diversi file, sarai in grado di vedere la modifica completa sullo schermo allo stesso tempo. Questo è il risultato di vedere la creazione / installazione / configurazione come una propria responsabilità - non è compito di un'auto costruire un motore.

Considerazioni sulle prestazioni

o questo influenza le prestazioni nel loro insieme?

No . Ma per essere completamente onesto con te, potrebbe.

Tuttavia, anche in quel caso, è una quantità così ridicolmente piccola che non devi preoccuparti. Se ad un certo punto, in futuro, si deve scrivere codice per un tamagotchi con l'equivalente di 5 Mhz CPU e 2MB di RAM, allora forse si potrebbe avere a preoccuparsi di questo.

Nel 99,999% * dei casi avrà prestazioni migliori, perché hai impiegato meno tempo a correggere i bug e più tempo a migliorare i tuoi algoritmi ricchi di risorse.

* numero completamente inventato

Informazioni aggiunte: "hard-coded"

Non commettere errori, questo è ancora molto "hard-coded" - i numeri sono scritti direttamente nel codice. Non codificato significherebbe qualcosa come archiviare quei valori in un file di testo, ad esempio in formato JSON, e poi leggerli da quel file.

Per fare ciò, è necessario aggiungere codice per leggere un file e quindi analizzare JSON. Se consideri di nuovo l'esempio; nella versione non DI, a Caro a Motorora deve leggere un file. Non sembra che abbia troppo senso.

Nella versione DI, lo aggiungeresti al codice che imposta il gioco.


3
Annuncio hardcoded, non c'è davvero molta differenza tra code e file di configurazione. Un file in bundle con l'applicazione è una fonte anche se si legge in modo dinamico. L'estrazione di valori dal codice a file di dati in formato json o in qualsiasi formato "config" non aiuta a meno che i valori non debbano essere sovrascritti dall'utente o dipendere dall'ambiente.
Jan Hudec,

3
In realtà ho fatto un Tamagotchi una volta su un Arduino (16MHz, 2KB)
Jungkook

@JanHudec True. In realtà ho avuto una spiegazione più lunga lì, ma ho deciso di rimuoverlo per mantenerlo più breve e concentrarmi su come si riferisce a DI. C'è più roba che non è corretta al 100%; nel complesso la risposta è più ottimizzata per spingere "il punto" di DI senza che diventi troppo lungo. O in altre parole, questo è quello che avrei voluto sentire quando ho iniziato con DI.
R. Schmitz,

17

Sono sempre stato sconcertato dall'iniezione di dipendenza. Sembrava esistere solo all'interno delle sfere Java, ma quelle sfere ne parlavano con grande riverenza. È stato uno dei grandi Pattern, vedi, che si dice porti ordine nel caos. Ma gli esempi sono sempre stati contorti e artificiali, stabilendo un non-problema e poi iniziando a risolverlo rendendo il codice più complicato.

Aveva più senso quando un altro sviluppatore di Python mi ha impartito questa saggezza: sta solo passando argomenti alle funzioni. È a malapena un modello affatto; più come un promemoria che puoi chiedere qualcosa come argomento, anche se avresti potuto fornire te stesso un valore ragionevole.

Quindi la tua domanda è approssimativamente equivalente a "perché la mia funzione dovrebbe prendere argomenti?" e ha molte delle stesse risposte, vale a dire: lasciare che il chiamante prenda decisioni .

Questo ha un costo, ovviamente, perché ora stai forzando il chiamante a prendere una decisione (a meno che tu non renda l'argomento facoltativo) e l'interfaccia sia un po 'più complessa. In cambio, ottieni flessibilità.

Quindi: c'è una buona ragione per cui è necessario utilizzare questo particolare Settingtipo / valore? C'è una buona ragione per cui il codice chiamante potrebbe volere un Settingtipo / valore diverso ? (Ricorda, i test sono codice !)


2
Sì. IoC alla fine mi ha cliccato quando ho capito che si trattava semplicemente di "lasciare che il chiamante prendesse decisioni". Che IoC significa che il controllo del componente viene spostato dall'autore del componente all'utente del componente. E poiché a quel punto ho già avuto abbastanza problemi con un software che si riteneva più intelligente di me, sono stato immediatamente venduto con l'approccio DI.
Joker_vD

1
"sta solo passando argomenti alle funzioni. È a malapena un modello" Questa è l'unica spiegazione buona o persino comprensibile di DI che ho sentito (beh, la maggior parte delle persone non parla nemmeno di cosa sia , ma piuttosto dei suoi benefici). E poi usano un gergo complicato come "inversione di controllo" e "accoppiamento lento" che richiedono solo più domande per capire quando è un'idea davvero semplice.
addison

7

L'esempio che dai non è l'iniezione di dipendenza in senso classico. L'iniezione di dipendenza di solito si riferisce al passaggio di oggetti in un costruttore o utilizzando "iniezione setter" subito dopo la creazione dell'oggetto, al fine di impostare un valore su un campo in un oggetto appena creato.

Il tuo esempio passa un oggetto come argomento a un metodo di istanza. Questo metodo di istanza quindi modifica un campo su quell'oggetto. Iniezione di dipendenza? No. Rompere l'incapsulamento e nascondere i dati? Assolutamente!

Ora, se il codice fosse così:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Quindi direi che stai usando l'iniezione di dipendenza. La notevole differenza è che un Settingsoggetto viene passato al costruttore o a un Profileoggetto.

Ciò è utile se l'oggetto Settings è costoso o complesso da costruire, oppure Settings è un'interfaccia o una classe astratta in cui esistono più implementazioni concrete per modificare il comportamento in fase di esecuzione.

Poiché si accede direttamente a un campo sull'oggetto Impostazioni anziché chiamare un metodo, non è possibile sfruttare il polimorfismo, che è uno dei vantaggi dell'iniezione di dipendenza.

Sembra che le impostazioni per un profilo siano specifiche per quel profilo. In questo caso, farei una delle seguenti operazioni:

  1. Crea un'istanza dell'oggetto Impostazioni all'interno del costruttore Profilo

  2. Passa l'oggetto Impostazioni nel costruttore e copia sui singoli campi che si applicano al profilo

Onestamente, passando l'oggetto Impostazioni in deactivateProfilee quindi modificando un campo interno dell'oggetto Impostazioni è un odore di codice. L'oggetto Impostazioni dovrebbe essere l'unico che modifica i suoi campi interni.


5
The example you give is not dependency injection in the classical sense.- Non importa. Le persone OO hanno oggetti nel cervello, ma stai ancora dando una dipendenza a qualcosa.
Robert Harvey,

1
Quando parli di " in senso classico ", stai, come dice @RobertHarvey, parlando puramente in termini di OO. Nella programmazione funzionale, ad esempio, l'iniezione di una funzione in un'altra (funzioni di ordine superiore) è il classico esempio di iniezione di dipendenza di quel paradigma.
David Arno,

3
@RobertHarvey: Immagino che mi stavo prendendo troppe libertà con "iniezione di dipendenza". Il luogo in cui le persone più spesso pensano al termine e lo usano è in riferimento ai campi su un oggetto che viene "iniettato" al momento della costruzione dell'oggetto, o immediatamente dopo nel caso dell'iniezione del setter.
Greg Burghardt,

@DavidArno: Sì, hai ragione. L'OP sembra avere uno snippet di codice PHP orientato agli oggetti nella domanda, quindi stavo rispondendo solo a questo proposito e non rivolgevo la programmazione funzionale --- tutto sebbene con PHP potessi porre la stessa domanda dal punto di vista della programmazione funzionale.
Greg Burghardt,

7

So che arrivo tardi a questa festa, ma sento che manca un punto importante.

Perché dovrei farlo:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Non dovresti. Ma non perché l'iniezione di dipendenza è una cattiva idea. È perché sta sbagliando.

Vediamo queste cose usando il codice. Faremo questo:

$profile = new Profile();
$profile->deactivateProfile($setting);

quando ne ricaviamo la stessa cosa:

$setting->isActive = false; // Deactivate profile

Quindi ovviamente sembra una perdita di tempo. È quando lo fai in questo modo. Questo non è il miglior uso dell'iniezione di dipendenza. Non è nemmeno il miglior uso di una classe.

E se invece avessimo questo:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

E ora il applicationè libero di attivare e disattivare il profilesenza dover sapere nulla in particolare di settingciò che sta effettivamente cambiando. Perché va bene? Nel caso in cui sia necessario modificare le impostazioni. Ilapplication è murato fuori da tali modifiche in modo sei libero di andare noci in uno spazio contenuto sicuro senza dover guardare tutto rompere non appena si tocca qualcosa.

Ciò segue la costruzione separata dal comportamento principio di . Il modello DI qui è semplice. Costruisci tutto ciò di cui hai bisogno a un livello il più basso possibile, collegali insieme, quindi inizia tutto il ticchettio del comportamento con una chiamata.

Il risultato è che hai un posto separato per decidere cosa si collega a cosa e un posto diverso per gestire cosa dice cosa a qualunque cosa.

Provalo su qualcosa che devi mantenere nel tempo e vedi se non aiuta.


6

Come cliente, quando assumi un meccanico per fare qualcosa alla tua auto, ti aspetti che quel meccanico costruisca un'auto da zero solo per poi lavorare con essa? No, dai al meccanico l'auto su cui vuoi che lavori .

Come proprietario del garage, quando istruisci un meccanico a fare qualcosa a un'auto, ti aspetti che il meccanico crei le sue parti di cacciavite / chiave inglese / auto? No, fornisci al meccanico le parti / gli strumenti che deve usare

Perché lo facciamo? Ci pensiamo. Sei il proprietario di un garage che vuole assumere qualcuno per diventare il tuo meccanico. Insegnerai loro ad essere meccanici (= scriverai il codice).

Cosa sarà più facile:

  • Insegna a un meccanico come collegare uno spoiler a un'auto usando un cacciavite.
  • Insegnare a un meccanico a creare un'auto, creare uno spoiler, creare un cacciavite e quindi collegare lo spoiler appena creato all'auto appena creata con il cacciavite appena creato.

Ci sono enormi vantaggi nel non avere il tuo meccanico che crea tutto da zero:

  • Ovviamente, l'addestramento (= sviluppo) si riduce drasticamente se fornisci al tuo meccanico strumenti e parti esistenti.
  • Se lo stesso meccanico deve eseguire lo stesso lavoro più volte, puoi assicurarti che riutilizzi il cacciavite invece di buttare sempre quello vecchio e crearne uno nuovo.
  • Inoltre, un meccanico che ha imparato a creare tutto dovrà essere molto più esperto, e quindi si aspetterà un salario più elevato. L'analogia con il codice qui è che una classe con molte responsabilità è molto più difficile da mantenere rispetto a una classe con una sola responsabilità strettamente definita.
  • Inoltre, quando le nuove invenzioni colpiscono il mercato e gli spoiler vengono ora realizzati in carbonio anziché in plastica; dovrai riqualificarti (= riqualificare) il tuo meccanico esperto. Ma il tuo "semplice" meccanico non dovrà essere riqualificato fintanto che lo spoiler può ancora essere attaccato allo stesso modo.
  • Avere un meccanico che non fa affidamento su un'auto che hanno costruito da soli significa che hai un meccanico che è in grado di gestire qualsiasi auto che possano ricevere. Comprese le auto che non esistevano ancora al momento dell'addestramento del meccanico. Tuttavia, il tuo meccanico esperto non sarà in grado di costruire auto più nuove che sono state create dopo l'allenamento.

Se assumi e addestra il meccanico esperto, finirai con un dipendente che costa di più, impiega più tempo a svolgere quello che dovrebbe essere un lavoro semplice e dovrà essere continuamente riqualificato ogni volta che uno dei tanti responsabilità ha bisogno per essere aggiornato.

L'analogia di sviluppo è che se usi le classi con dipendenze hardcoded, finirai con classi difficili da mantenere che avranno bisogno di continue riqualificazioni / modifiche ogni volta che Settingsviene creata una nuova versione dell'oggetto ( nel tuo caso), e tu dovrò sviluppare una logica interna affinché la classe abbia la capacità di creare diversi tipi di Settingsoggetti.

Inoltre, chiunque consuma la tua classe ora dovrà anche chiedere alla classe di creare l' Settingsoggetto corretto , invece di essere semplicemente in grado di passare alla classe qualsiasi Settingsoggetto che desideri passare. Ciò significa ulteriore sviluppo per il consumatore per capire come chiedere alla classe di creare lo strumento giusto.


Sì, l'inversione della dipendenza richiede un po 'più di sforzo per scrivere invece di codificare la dipendenza. Sì, è fastidioso dover scrivere di più.

Ma questo è lo stesso argomento della scelta di codificare i valori letterali perché "dichiarare le variabili richiede più sforzo". Tecnicamente corretto, ma i professionisti superano i contro di diversi ordini di grandezza .

Il vantaggio dell'inversione di dipendenza non si verifica quando si crea la prima versione dell'applicazione. Il vantaggio dell'inversione di dipendenza si verifica quando è necessario modificare o estendere quella versione iniziale. E non indurti a pensare che la prima volta lo otterrai correttamente e non dovrai estendere / modificare il codice. Dovrai cambiare le cose.


questo influenza le prestazioni nel loro insieme?

Ciò non influisce sulle prestazioni di runtime dell'applicazione. Ma ha un impatto enorme sui tempi di sviluppo (e quindi sulle prestazioni) dello sviluppatore .


2
Se hai rimosso il tuo commento, " Come commento secondario, la tua domanda si concentra sull'inversione di dipendenza, non sull'iniezione di dipendenza. L'iniezione è un modo per fare l'inversione, ma non è l'unico modo ", questa sarebbe una risposta eccellente. L'inversione delle dipendenze può essere ottenuta tramite iniezione o localizzatore / globuli. Gli esempi della domanda riguardano l'iniezione. Quindi la domanda è su iniezione di dipendenza (così come la dipendenza inversione).
David Arno,

12
Penso che l'intera cosa dell'auto sia un po 'sconcertata e confusa
Ewan,

4
@Flater, parte del problema è che nessuno sembra essere veramente d'accordo su quale sia la differenza tra iniezione di dipendenza, inversione di dipendenza e inversione di controllo. Una cosa è certa, tuttavia, un "contenitore" sicuramente non è necessario per iniettare una dipendenza in un metodo o costruttore. DI puro (o povero) descrive specificamente l'iniezione manuale di dipendenza. È l'unica iniezione di dipendenza che uso personalmente poiché non mi piace la "magia" associata ai contenitori.
David Arno,

1
@Flater Il problema con l'esempio è che quasi certamente non modelleresti alcun problema nel codice in questo modo. È quindi innaturale, forzato e aggiunge più domande che risposte. Ciò rende più probabile fuorviare e confondere che dimostrare.
jpmc26,

2
@gbjbaanb: la mia risposta non approfondisce nemmeno in remoto il polimorfismo. Non esiste ereditarietà, implementazione dell'interfaccia o qualcosa che assomigli all'upgrade / downcast dei tipi. Stai fraintendendo completamente la risposta.
Flater,

0

Come tutti i motivi, è molto valido chiedere "perché" per evitare disegni gonfiati.

Per l'iniezione di dipendenza, questo è facilmente visibile pensando ai due, probabilmente, aspetti più importanti del design OOP ...

Accoppiamento basso

Accoppiamento nella programmazione informatica :

Nell'ingegneria del software, l'accoppiamento è il grado di interdipendenza tra i moduli software; una misura di quanto siano strettamente collegate due routine o moduli; la forza delle relazioni tra i moduli.

Vuoi ottenere basso accoppiamento . Due cose che sono fortemente accoppiate significano che se cambi l'una, molto probabilmente dovrai cambiare l'altra. Bug o restrizioni nell'uno probabilmente indurranno bug / restrizioni nell'altro; e così via.

Una classe che crea istanze di oggetti degli altri è un accoppiamento molto forte, perché l'uno deve sapere dell'esistenza dell'altro; deve sapere come istanziarlo (di quali argomenti ha bisogno il costruttore) e tali argomenti devono essere disponibili quando si chiama il costruttore. Inoltre, a seconda che il linguaggio necessiti di decostruzione esplicita (C ++), ciò introdurrà ulteriori complicazioni. Se si introducono nuove classi (ad es. ANextSettings o altro), è necessario tornare alla classe originale e aggiungere più chiamate ai loro costruttori.

Alta coesione

Coesione :

Nella programmazione per computer, la coesione si riferisce al grado in cui gli elementi all'interno di un modulo appartengono insieme.

Questo è l'altro lato della medaglia. Se osservi un'unità di codice (un metodo, una classe, un pacchetto ecc.), Vuoi che tutto il codice all'interno di quell'unità abbia il minor numero di responsabilità possibile.

Un esempio di base per questo sarebbe il modello MVC: separi chiaramente il modello di dominio dalla vista (GUI) e un livello di controllo che li incolla.

Questo evita che il codice si gonfi quando ottieni grossi pezzi che fanno molte cose diverse; se si desidera modificarne una parte, è necessario tenere traccia anche di tutte le altre funzionalità, cercando di evitare bug, ecc .; e ti programmi rapidamente in un buco in cui è molto difficile uscire.

Con l'iniezione delle dipendenze, deleghi la creazione o tenendo traccia delle dipendenze, indipendentemente dalle classi (o dai file di configurazione) che implementano il tuo DI. Le altre classi non si preoccuperanno molto di quello che sta succedendo esattamente - lavoreranno con alcune interfacce generiche e non hanno idea di quale sia l'implementazione effettiva, il che significa che non sono responsabili delle altre cose.


-1

Dovresti usare tecniche per risolvere i problemi che sono bravi a risolvere quando hai quei problemi. L'inversione e l'iniezione delle dipendenze non sono diverse.

L'inversione o iniezione di dipendenza è una tecnica che consente al codice di decidere quale implementazione di un metodo viene chiamata in fase di esecuzione. Ciò massimizza i benefici dell'associazione tardiva. La tecnica è necessaria quando la lingua non supporta la sostituzione in fase di esecuzione di funzioni non di istanza. Ad esempio, Java non ha un meccanismo per sostituire le chiamate a un metodo statico con chiamate a un'implementazione diversa; contrasto con Python, dove tutto ciò che è necessario per sostituire la chiamata di funzione è associare il nome a un'altra funzione (riassegnare la variabile che contiene la funzione).

Perché dovremmo voler variare l'implementazione della funzione? Vi sono due ragioni principali:

  • Vogliamo usare i falsi a scopo di test. Questo ci consente di testare una classe che dipende da un recupero del database senza effettivamente collegarsi al database.
  • Dobbiamo supportare più implementazioni. Ad esempio, potrebbe essere necessario configurare un sistema che supporti sia i database MySQL sia PostgreSQL.

È inoltre possibile prendere nota dell'inversione dei contenitori di controllo. Questa è una tecnica che ti aiuterà a evitare enormi alberi da costruzione aggrovigliati che assomigliano a questo pseudocodice:

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

Ti consente di registrare le tue lezioni e quindi fa la costruzione per te:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

Si noti che è più semplice se le classi registrate possono essere singoli senza stato .

Avvertenza

Si noti che l'inversione di dipendenza non dovrebbe essere la risposta ideale per la logica di disaccoppiamento. Cerca invece opportunità per usare la parametrizzazione . Considera questo metodo pseudocodice per esempio:

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

Potremmo usare l'inversione di dipendenza per alcune parti di questo metodo:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

Ma non dovremmo, almeno non del tutto. Si noti che abbiamo creato una classe stateful con Querier. Ora contiene un riferimento ad alcuni oggetti di connessione essenzialmente globali. Ciò crea problemi come difficoltà a comprendere lo stato generale del programma e il modo in cui le diverse classi si coordinano tra loro. Si noti inoltre che siamo costretti a falsificare la query o la connessione se vogliamo testare la logica della media. Ulteriori Un approccio migliore sarebbe quello di aumentare la parametrizzazione :

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

E la connessione verrebbe gestita ad un livello ancora più alto che è responsabile dell'intera operazione e sa cosa fare con questo output.

Ora possiamo testare la logica della media in modo completamente indipendente dalla query e, inoltre, possiamo usarla in una più ampia varietà di situazioni. Potremmo chiederci se abbiamo persino bisogno degli oggetti MyQueriere Averager, e forse la risposta è che non lo facciamo se non intendiamo test unitari StuffDoer, e non i test unitari StuffDoersarebbero perfettamente ragionevoli dal momento che sono così strettamente accoppiati al database. Potrebbe avere più senso lasciare che i test di integrazione lo coprano. In tal caso, potremmo andare bene fare fetchAboveMine averageDatain metodi statici.


2
"L' iniezione di dipendenza è una tecnica che ha lo scopo di aiutarti a evitare enormi alberi da costruzione aggrovigliati ... ". Il tuo primo esempio dopo questa affermazione è un esempio di iniezione di dipendenza pura o povera. Il secondo è un esempio dell'uso di un contenitore IoC per "automatizzare" l'iniezione di tali dipendenze. Entrambi sono esempi di iniezione di dipendenza in azione.
David Arno,

@DavidArno Sì, hai ragione. Ho modificato la terminologia.
jpmc26,

C'è un terzo motivo principale per variare l'implementazione, o almeno progettare il codice supponendo che l'implementazione possa variare: incentiva lo sviluppatore ad avere un accoppiamento lento ed evita di scrivere codice che sarà difficile cambiare se una nuova implementazione dovesse essere implementata in qualche momento in il futuro. E mentre ciò potrebbe non essere una priorità in alcuni progetti (ad esempio, sapendo che l'applicazione non verrà mai rivisitata dopo il suo rilascio iniziale), sarà in altri (ad esempio, dove il modello di business dell'azienda cerca specificamente di offrire supporto / estensione estesi delle loro applicazioni ).
Flater,

L'iniezione di dipendenza @Flater comporta ancora un forte accoppiamento. Lega la logica a una particolare interfaccia e richiede che il codice in questione sia a conoscenza di qualunque cosa faccia quell'interfaccia. Considera il codice che trasforma i risultati recuperati da un DB. Se uso DI per separarli, il codice deve ancora sapere che avviene un recupero DB e invocarlo. La soluzione migliore è se il codice di trasformazione non sa nemmeno che sta avvenendo un recupero . L'unico modo per farlo è se i risultati del recupero vengono passati da un chiamante, invece di iniettare il recupero.
jpmc26,

1
@ jpmc26 Ma nel tuo esempio (commento) il database non ha nemmeno bisogno di essere una dipendenza per cominciare. Ovviamente evitare le dipendenze è meglio per l'accoppiamento libero, ma DI si concentra sull'implementazione delle dipendenze necessarie.
Flater,
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.