Qual è il principio di inversione di dipendenza e perché è importante?


178

Qual è il principio di inversione di dipendenza e perché è importante?



3
Ridicola quantità di risposte qui usando i termini "alto livello" e "basso livello" da Wikipedia. Questi termini sono inaccessibili e portano molti lettori a questa pagina. Se hai intenzione di rigurgitare Wikipedia, ti preghiamo di definire questi termini nelle risposte per dare un contesto!
8bitjunkie,

Risposte:


107

Dai un'occhiata a questo documento: il principio di inversione di dipendenza .

In pratica dice:

  • I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
  • Le astrazioni non dovrebbero mai dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Per quanto riguarda il motivo per cui è importante, in breve: le modifiche sono rischiose e, dipendendo da un concetto anziché da un'implementazione, si riduce la necessità di modifiche nei siti di chiamata.

In effetti, il DIP riduce l'accoppiamento tra diversi pezzi di codice. L'idea è che sebbene ci siano molti modi per implementare, diciamo, una funzione di registrazione, il modo in cui la useresti dovrebbe essere relativamente stabile nel tempo. Se riesci a estrarre un'interfaccia che rappresenta il concetto di registrazione, questa interfaccia dovrebbe essere molto più stabile nel tempo rispetto alla sua implementazione e i siti di chiamata dovrebbero essere molto meno influenzati dalle modifiche che potresti apportare mantenendo o estendendo quel meccanismo di registrazione.

Inoltre, facendo in modo che l'implementazione dipenda da un'interfaccia, avrai la possibilità di scegliere in fase di esecuzione quale implementazione sia più adatta al tuo particolare ambiente. A seconda dei casi, potrebbe essere interessante anche questo.


31
Questa risposta non dice perché DIP è importante, o anche cosa sia DIP. Ho letto il documento DIP ufficiale e penso che questo sia davvero un "principio" scarso e ingiustificato, perché basato su un presupposto errato: i moduli di alto livello sono riutilizzabili.
Rogério,

3
Considera un grafico di dipendenza per alcuni oggetti. Applica DIP agli oggetti. Ora qualsiasi oggetto sarà indipendente dall'implementazione degli altri oggetti. Il test dell'unità è ora semplice. È possibile un successivo refactoring per il riutilizzo. Le modifiche di progettazione hanno ambiti di modifica molto limitati. I problemi di progettazione non si sovrappongono. Vedi anche il modello AI "Lavagna" per l'inversione della dependaecy dei dati. Insieme, strumenti molto potenti per rendere il software comprensibile, gestibile e affidabile. Ignora l'iniezione di dipendenza in questo contesto. Non è correlato.
Tim Williscroft,

6
Una classe A che utilizza una classe B non significa che A "dipende" dall'implementazione di B; dipende solo dall'interfaccia pubblica di B. L'aggiunta di un'astrazione separata da cui ora dipendono sia A che B significa solo che A non avrà più una dipendenza in fase di compilazione dall'interfaccia pubblica di B. L'isolamento tra le unità può essere raggiunto facilmente senza queste astrazioni aggiuntive; ci sono strumenti di derisione specifici per questo, in Java e .NET, che affrontano tutte le situazioni (metodi statici, costruttori, ecc.). L'applicazione del DIP tende a rendere il software più complesso, meno gestibile e non più testabile.
Rogério,

1
Allora cos'è l'inversione del controllo? un modo per ottenere l'inversione delle dipendenze?
user20358

2
@Rogerio, vedi la risposta di Derek Greer di seguito, lo spiega. AFAIK, DIP afferma che è A che impone ciò di cui A ha bisogno, e non B che dice ciò di cui A ha bisogno. Quindi, l'interfaccia di cui A ha bisogno non dovrebbe essere data da B, ma per A.
ejaenv

145

I libri Agile Software Development, Principles, Patterns and Practices e Agile Principles, Patterns and Practices in C # sono le migliori risorse per comprendere appieno gli obiettivi e le motivazioni originali alla base del principio di inversione di dipendenza. L'articolo "The Dependency Inversion Principle" è anche una buona risorsa, ma a causa del fatto che si tratta di una versione ridotta di una bozza che alla fine si è fatta strada nei libri precedentemente menzionati, lascia fuori alcune importanti discussioni sul concetto di un la proprietà del pacchetto e dell'interfaccia che sono fondamentali per distinguere questo principio dai consigli più generali di "programmare su un'interfaccia, non un'implementazione" che si trova nel libro Design Patterns (Gamma, et. al).

Per fornire un riepilogo, il principio di inversione di dipendenza riguarda principalmente l' inversione della direzione convenzionale delle dipendenze da componenti di "livello superiore" a componenti di "livello inferiore" in modo tale che i componenti di "livello inferiore" dipendono dalle interfacce di proprietà dei componenti di "livello superiore" . (Nota: il componente "livello superiore" qui si riferisce al componente che richiede dipendenze / servizi esterni, non necessariamente alla sua posizione concettuale all'interno di un'architettura a strati.) In tal modo, l'accoppiamento non si riduce tanto quanto viene spostato da componenti teoricamente meno prezioso per i componenti che sono teoricamente più preziosi.

Ciò si ottiene progettando componenti le cui dipendenze esterne sono espresse in termini di un'interfaccia per la quale un'implementazione deve essere fornita dal consumatore del componente. In altre parole, le interfacce definite esprimono ciò che è necessario al componente, non il modo in cui si utilizza il componente (ad es. "INeedSomething", non "IDoSomething").

Ciò a cui il principio di inversione di dipendenza non fa riferimento è la semplice pratica di astrarre dipendenze attraverso l'uso di interfacce (ad esempio MyService → [ILogger ⇐ Logger]). Mentre questo separa un componente dai dettagli di implementazione specifici della dipendenza, non inverte la relazione tra il consumatore e la dipendenza (ad esempio [MyService → IMyServiceLogger] ⇐ Logger.

L'importanza del principio di inversione di dipendenza può essere distillata fino a un unico obiettivo di poter riutilizzare i componenti software che si basano su dipendenze esterne per una parte della loro funzionalità (registrazione, convalida, ecc.)

All'interno di questo obiettivo generale di riutilizzo, possiamo delineare due sottotipi di riutilizzo:

  1. Utilizzo di un componente software all'interno di più applicazioni con implementazioni di sub-dipendenza (ad esempio, è stato sviluppato un contenitore DI e si desidera fornire la registrazione, ma non si desidera associare il proprio contenitore a un logger specifico in modo tale che anche tutti coloro che usano il proprio contenitore devono utilizzare la libreria di registrazione scelta).

  2. Utilizzo di componenti software in un contesto in evoluzione (ad esempio, sono stati sviluppati componenti di logica aziendale che rimangono gli stessi su più versioni di un'applicazione in cui i dettagli di implementazione stanno evolvendo).

Con il primo caso di riutilizzo di componenti tra più applicazioni, ad esempio con una libreria di infrastruttura, l'obiettivo è fornire un'esigenza di infrastruttura di base ai tuoi consumatori senza associare i tuoi consumatori a sotto-dipendenze della tua biblioteca poiché prendere dipendenze da tali dipendenze richiede anche i consumatori richiedono le stesse dipendenze. Ciò può essere problematico quando gli utenti della tua biblioteca scelgono di utilizzare una libreria diversa per le stesse esigenze di infrastruttura (ad esempio NLog vs. log4net) o se scelgono di utilizzare una versione successiva della libreria richiesta che non è compatibile con la versione precedente richiesto dalla tua biblioteca.

Con il secondo caso di riutilizzo di componenti di logica aziendale (ovvero "componenti di livello superiore"), l'obiettivo è quello di isolare l'implementazione del dominio principale dell'applicazione dalle mutevoli esigenze dei dettagli dell'implementazione (ovvero modifica / aggiornamento delle librerie di persistenza, librerie di messaggistica , strategie di crittografia, ecc.). Idealmente, la modifica dei dettagli di implementazione di un'applicazione non dovrebbe interrompere i componenti che incapsulano la logica aziendale dell'applicazione.

Nota: alcuni potrebbero obiettare a descrivere questo secondo caso come un reale riutilizzo, ragionando sul fatto che componenti come i componenti della logica aziendale utilizzati all'interno di una singola applicazione in evoluzione rappresentano solo un singolo utilizzo. L'idea qui, tuttavia, è che ogni modifica ai dettagli di implementazione dell'applicazione rende un nuovo contesto e quindi un caso d'uso diverso, sebbene gli obiettivi finali possano essere distinti come isolamento vs. portabilità.

Mentre seguire il principio di inversione di dipendenza in questo secondo caso può offrire qualche vantaggio, va notato che il suo valore applicato ai linguaggi moderni come Java e C # è molto ridotto, forse al punto da essere irrilevante. Come discusso in precedenza, il DIP implica la separazione completa dei dettagli di implementazione in pacchetti separati. Nel caso di un'applicazione in evoluzione, tuttavia, il semplice utilizzo di interfacce definite in termini di dominio aziendale eviterà la necessità di modificare componenti di livello superiore a causa delle mutate esigenze dei componenti dei dettagli di implementazione, anche se i dettagli di implementazione risiedono in definitiva nello stesso pacchetto . Questa parte del principio riflette aspetti che erano pertinenti alla lingua in vista quando il principio era codificato (cioè C ++) che non sono rilevanti per le nuove lingue. Detto ciò,

Una discussione più lunga di questo principio in relazione al semplice uso di interfacce, Iniezione delle dipendenze e modello di interfaccia separata è disponibile qui . Inoltre, una discussione su come il principio si riferisce a linguaggi tipicamente dinamici come JavaScript può essere trovato qui .


17
Grazie. Vedo ora come la mia risposta non risponda al punto. La differenza tra MyService → [ILogger ⇐ Logger] e [MyService → IMyServiceLogger] ⇐ Logger è sottile ma importante.
Patrick McElhaney,

2
Nella stessa linea è molto ben spiegato qui: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
Come avrei voluto che le persone smettessero di usare i logger come base canonica per l'iniezione delle dipendenze. Soprattutto in relazione a log4net dove è quasi un anti pattern. A parte questo, spassosa spiegazione!
Casper Leon Nielsen,

4
@ Casper Leon Nielsen - DIP non ha nulla a che fare con DI Non sono sinonimi né concetti equivalenti.
TSmith,

4
@ VF1 Come indicato nel paragrafo di sintesi, l'importanza del principio di inversione di dipendenza è principalmente con il riutilizzo. Se John rilascia una libreria che ha una dipendenza esterna da una libreria di registrazione e Sam desidera utilizzare la libreria di John, Sam assume la dipendenza di registrazione temporanea. Sam non sarà mai in grado di distribuire la sua applicazione senza la libreria di registrazione selezionata da John. Se John segue il DIP, tuttavia, Sam è libero di fornire un adattatore e utilizzare qualsiasi libreria di registrazione che sceglie. Il DIP non è per comodità, ma accoppiamento.
Derek Greer,

14

Quando progettiamo applicazioni software possiamo considerare le classi di basso livello le classi che implementano operazioni di base e primarie (accesso al disco, protocolli di rete, ...) e le classi di alto livello le classi che incapsulano la logica complessa (flussi di business, ...).

Gli ultimi si basano su classi di basso livello. Un modo naturale di implementare tali strutture sarebbe quello di scrivere classi di basso livello e una volta che le abbiamo per scrivere le classi complesse di alto livello. Poiché le classi di alto livello sono definite in termini di altre, questo sembra il modo logico per farlo. Ma questo non è un design flessibile. Cosa succede se dobbiamo sostituire una classe di basso livello?

Il principio di inversione di dipendenza afferma che:

  • I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
  • Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Questo principio cerca di "invertire" l'idea convenzionale secondo cui i moduli di alto livello nel software dovrebbero dipendere dai moduli di livello inferiore. Qui i moduli di alto livello possiedono l'astrazione (ad esempio, decidendo i metodi dell'interfaccia) che sono implementati dai moduli di livello inferiore. In questo modo i moduli di livello inferiore dipendono dai moduli di livello superiore.


11

L'inversione delle dipendenze ben applicata offre flessibilità e stabilità a livello dell'intera architettura dell'applicazione. Permetterà all'applicazione di evolversi in modo più sicuro e stabile.

Architettura tradizionale a strati

Tradizionalmente un'interfaccia utente di architettura a più livelli dipendeva dal livello aziendale e questo a sua volta dipendeva dal livello di accesso ai dati.

Devi comprendere livello, pacchetto o libreria. Vediamo come sarebbe il codice.

Avremmo una libreria o un pacchetto per il livello di accesso ai dati.

// DataAccessLayer.dll
public class ProductDAO {

}

E un'altra logica aziendale di libreria o livello pacchetto che dipende dal livello di accesso ai dati.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Architettura a strati con inversione di dipendenza

L'inversione di dipendenza indica quanto segue:

I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.

Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Quali sono i moduli di alto livello e basso livello? Moduli pensanti come librerie o pacchetti, modulo di alto livello sarebbero quelli che tradizionalmente hanno dipendenze e basso livello da cui dipendono.

In altre parole, il livello alto del modulo è dove viene invocata l'azione e il livello basso dove viene eseguita l'azione.

Una conclusione ragionevole da trarre da questo principio è che non dovrebbe esserci dipendenza tra concrezioni, ma deve esserci dipendenza da un'astrazione. Ma secondo l'approccio che adottiamo possiamo applicare erroneamente la dipendenza dagli investimenti, ma un'astrazione.

Immagina di adattare il nostro codice come segue:

Avremmo una libreria o un pacchetto per il livello di accesso ai dati che definiscono l'astrazione.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

E un'altra logica aziendale di libreria o livello pacchetto che dipende dal livello di accesso ai dati.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Sebbene dipendiamo da una dipendenza di astrazione tra business e accesso ai dati rimane lo stesso.

Per ottenere l'inversione di dipendenza, l'interfaccia di persistenza deve essere definita nel modulo o nel pacchetto in cui si trova questa logica o dominio di alto livello e non nel modulo di basso livello.

Definisci innanzitutto qual è il livello di dominio e l'astrazione della sua comunicazione è definita persistenza.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Dopo che il livello di persistenza dipende dal dominio, ora è possibile invertire se viene definita una dipendenza.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(fonte: xurxodev.com )

Approfondimento del principio

È importante assimilare bene il concetto, approfondendo lo scopo e i benefici. Se rimaniamo meccanicamente e apprendiamo il tipico repository di casi, non saremo in grado di identificare dove possiamo applicare il principio di dipendenza.

Ma perché invertire una dipendenza? Qual è l'obiettivo principale al di là di esempi specifici?

Questo comunemente consente alle cose più stabili, che non dipendono da cose meno stabili, di cambiare più frequentemente.

È più facile modificare il tipo di persistenza, sia il database che la tecnologia accedono allo stesso database rispetto alla logica del dominio o alle azioni progettate per comunicare con persistenza. Per questo motivo, la dipendenza viene invertita perché poiché è più facile cambiare la persistenza se si verifica questo cambiamento. In questo modo non dovremo cambiare il dominio. Il livello di dominio è il più stabile di tutti, motivo per cui non dovrebbe dipendere da nulla.

Ma non esiste solo questo esempio di repository. Ci sono molti scenari in cui si applica questo principio e ci sono architetture basate su questo principio.

architetture

Ci sono architetture in cui l'inversione di dipendenza è la chiave della sua definizione. In tutti i domini è il più importante ed è l'astrazione che indicherà che il protocollo di comunicazione tra il dominio e il resto dei pacchetti o delle librerie sono definiti.

Architettura pulita

Nell'architettura pulita il dominio si trova al centro e se si guarda nella direzione delle frecce che indicano la dipendenza, è chiaro quali sono i livelli più importanti e stabili. Gli strati esterni sono considerati strumenti instabili, quindi evitare di dipenderli.


(fonte: 8thlight.com )

Architettura esagonale

Succede allo stesso modo con l'architettura esagonale, in cui il dominio si trova anche nella parte centrale e le porte sono astrazioni di comunicazione dal domino verso l'esterno. Anche in questo caso è evidente che il dominio è la più stabile e la dipendenza tradizionale è invertita.


Non sono sicuro di cosa significhi dire, ma questa frase è incoerente: "Ma secondo l'approccio che adottiamo possiamo applicare erroneamente la dipendenza dagli investimenti, ma un'astrazione". Forse ti piacerebbe risolvere?
Mark Amery, il

9

Per me, il principio di inversione di dipendenza, come descritto nell'articolo ufficiale , è in realtà un tentativo fuorviato di aumentare la riusabilità dei moduli intrinsecamente meno riutilizzabili, nonché un modo per aggirare un problema nel linguaggio C ++.

Il problema in C ++ è che i file header in genere contengono dichiarazioni di campi e metodi privati. Pertanto, se un modulo C ++ di alto livello include il file di intestazione per un modulo di basso livello, dipenderà dai dettagli di implementazione effettivi di quel modulo. E questo, ovviamente, non è una buona cosa. Ma questo non è un problema nelle lingue più moderne comunemente usate oggi.

I moduli di alto livello sono intrinsecamente meno riutilizzabili rispetto ai moduli di basso livello perché i primi sono normalmente più specifici per applicazione / contesto rispetto ai secondi. Ad esempio, un componente che implementa una schermata dell'interfaccia utente è di altissimo livello e anche molto (completamente?) Specifico per l'applicazione. Cercare di riutilizzare un componente del genere in un'applicazione diversa è controproducente e può solo portare a un eccesso di ingegneria.

Quindi, la creazione di un'astrazione separata allo stesso livello di un componente A che dipende da un componente B (che non dipende da A) può essere fatta solo se il componente A sarà davvero utile per il riutilizzo in diverse applicazioni o contesti. In caso contrario, l'applicazione di DIP sarebbe una cattiva progettazione.


Anche se l'astrazione di alto livello è utile solo nel contesto di tale applicazione, può avere valore quando si desidera sostituire l'implementazione reale con uno stub per il test (o fornire un'interfaccia da riga di comando a un'applicazione normalmente disponibile sul web)
Przemek Pokrywka,

3
DIP è importante in tutte le lingue e non ha nulla a che fare con lo stesso C ++. Anche se il tuo codice di alto livello non lascerà mai l'applicazione, DIP ti consente di contenere la modifica del codice quando aggiungi o modifichi un codice di livello inferiore. Ciò riduce sia i costi di manutenzione sia le conseguenze indesiderate del cambiamento. DIP è un concetto di livello superiore. Se non lo capisci, devi fare di più googling.
Dirk Bester,

3
Penso che tu abbia frainteso quello che ho detto sul C ++. Era solo una motivazione per DIP; senza dubbio è più generale di così. Si noti che l'articolo ufficiale sul DIP chiarisce che la motivazione centrale era supportare il riutilizzo di moduli di alto livello, rendendoli immuni ai cambiamenti nei moduli di basso livello; senza necessità di riutilizzo, è molto probabile che sia eccessivo e eccessivo. (L'hai letto? Parla anche del problema del C ++.)
Rogério,

1
Non stai trascurando l'applicazione inversa di DIP? Cioè, i moduli di livello superiore implementano la tua applicazione, essenzialmente. La tua preoccupazione principale non è quella di riutilizzarla, ma di renderla meno dipendente dalle implementazioni dei moduli di livello inferiore in modo che l'aggiornamento per tenere il passo con i cambiamenti futuri isola la tua applicazione dalle devastazioni del tempo e delle nuove tecnologie. Non è per rendere più semplice la sostituzione di WindowsOS, ma per rendere WindowsOS meno dipendente dai dettagli di implementazione di FAT / HDD in modo che i nuovi NTFS / SSD possano essere collegati a WindowsOS senza alcun impatto.
knockNrod

1
La schermata dell'interfaccia utente non è sicuramente il modulo di livello più alto o non dovrebbe essere per qualsiasi applicazione. I moduli di livello più alto sono quelli che contengono regole aziendali. Un modulo di livello più alto non è definito anche dal suo potenziale di riusabilità. Consulta la semplice spiegazione di zio Bob in questo articolo: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba,

8

Fondamentalmente dice:

La classe dovrebbe dipendere da astrazioni (ad es. Interfaccia, classi astratte), non da dettagli specifici (implementazioni).


Può essere così semplice? Ci sono lunghi articoli e persino libri, come menzionato da DerekGreer? In realtà stavo cercando una risposta semplice, ma è incredibile se è così semplice: D
Darius.V

1
@ darius-v non è un'altra versione del dire "usa astrazioni". Riguarda chi possiede le interfacce. Il principale afferma che il client (componenti di livello superiore) dovrebbe definire le interfacce e i componenti di livello inferiore dovrebbero implementarle.
Boran,

6

Buone risposte e buoni esempi sono già stati dati da altri qui.

Il motivo per cui DIP è importante è perché garantisce il principio OO "design debolmente accoppiato".

Gli oggetti nel software NON devono entrare in una gerarchia in cui alcuni oggetti sono quelli di livello superiore, a seconda degli oggetti di livello inferiore. Le modifiche agli oggetti di basso livello verranno quindi riordinate agli oggetti di livello superiore, il che rende il software molto fragile per il cambiamento.

Volete che i vostri oggetti di "livello superiore" siano molto stabili e non fragili per il cambiamento, quindi è necessario invertire le dipendenze.


6
Come fa esattamente DIP a farlo, per codice Java o .NET? In tali lingue, contrariamente al C ++, le modifiche all'implementazione di un modulo di basso livello non richiedono cambiamenti nei moduli di alto livello che lo utilizzano. Solo i cambiamenti nell'interfaccia pubblica sarebbero riordinati, ma poi anche l'astrazione definita al livello superiore dovrebbe cambiare.
Rogério,

5

Un modo molto più chiaro per affermare il principio di inversione di dipendenza è:

I moduli che incapsulano la logica aziendale complessa non dovrebbero dipendere direttamente da altri moduli che incapsulano la logica aziendale. Invece, dovrebbero dipendere solo dalle interfacce per i dati semplici.

Cioè, invece di implementare la tua classe Logiccome fanno le persone di solito:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

dovresti fare qualcosa del tipo:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Datae DataFromDependencydovrebbe vivere nello stesso modulo di Logic, non con Dependency.

Perché farlo

  1. I due moduli di business logic sono ora disaccoppiati. Quando Dependencycambia, non è necessario cambiare Logic.
  2. Capire cosa Logicfa è un compito molto più semplice: funziona solo su quello che sembra un ADT.
  3. Logicora può essere testato più facilmente. Ora puoi creare un'istanza direttamente Datacon dati falsi e trasferirli. Non sono necessarie simulazioni o impalcature di prova complesse.

Non penso sia giusto. Se DataFromDependency, che fa direttamente riferimento Dependency, si trova nello stesso modulo di Logic, allora il Logicmodulo dipende ancora direttamente dal Dependencymodulo al momento della compilazione. Per lo zio Bob la spiegazione del principio , evitando che questo sia il punto centrale di DIP. Piuttosto, per seguire DIP, Datadovrebbe essere nello stesso modulo di Logic, ma DataFromDependencydovrebbe essere nello stesso modulo di Dependency.
Mark Amery, il

3

Inversion of control (IoC) è un modello di progettazione in cui un oggetto ottiene la sua dipendenza da un framework esterno, piuttosto che chiedere a un framework la sua dipendenza.

Esempio di pseudocodice utilizzando la ricerca tradizionale:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Codice simile usando IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

I vantaggi di IoC sono:

  • Non si ha alcuna dipendenza da un framework centrale, quindi è possibile modificarlo se lo si desidera.
  • Poiché gli oggetti vengono creati mediante iniezione, preferibilmente utilizzando le interfacce, è facile creare unit test che sostituiscono le dipendenze con versioni simulate.
  • Disaccoppiamento del codice.

Il principio di inversione di dipendenza e l'inversione del controllo sono la stessa cosa?
Peter Mortensen,

3
L '"inversione" in DIP non è la stessa di Inversion of Control. Il primo riguarda le dipendenze in fase di compilazione, mentre il secondo riguarda il flusso di controllo tra i metodi in fase di esecuzione.
Rogério,

Sento che alla versione IoC manchi qualcosa. Come viene definito l'oggetto Database che viene definito, da dove proviene. Sto cercando di capire come questo non tarda a specificare tutte le dipendenze al massimo livello in una dipendenza da dio gigante
Richard Tingle,

1
Come già detto da @ Rogério, DIP non è DI / IoC. Questa risposta è errata IMO
zeraDev

1

Il punto di inversione di dipendenza è rendere software riutilizzabile.

L'idea è che invece di due parti di codice che si basano l'una sull'altra, si basano su un'interfaccia astratta. Quindi puoi riutilizzare uno dei due pezzi senza l'altro.

Il modo più comunemente raggiunto è attraverso un'inversione del contenitore di controllo (IoC) come Spring in Java. In questo modello, le proprietà degli oggetti sono impostate tramite una configurazione XML invece che gli oggetti escono e trovano la loro dipendenza.

Immagina questo pseudocodice ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass dipende direttamente sia dalla classe Service che dalla classe ServiceLocator. Ha bisogno di entrambi se si desidera utilizzarlo in un'altra applicazione. Ora immagina questo ...

public class MyClass
{
  public IService myService;
}

Ora, MyClass si basa su una singola interfaccia, l'interfaccia IService. Permetteremmo al contenitore IoC di impostare effettivamente il valore di quella variabile.

Quindi ora MyClass può essere facilmente riutilizzato in altri progetti, senza portare con sé la dipendenza di quelle altre due classi.

Ancora meglio, non devi trascinare le dipendenze di MyService, le dipendenze di quelle dipendenze e ... beh, hai capito.


2
Questa risposta non riguarda davvero DIP, ma qualcos'altro. Due parti di codice possono fare affidamento su un'interfaccia astratta e non l'una nell'altra, e comunque non seguono il principio di inversione di dipendenza.
Rogério,

1

Se possiamo dare per scontato che un dipendente di "alto livello" in una società è pagato per l'esecuzione dei suoi piani e che questi piani sono forniti dall'esecuzione aggregata di molti piani di dipendenti di "basso livello", allora potremmo dire è generalmente un piano terribile se la descrizione del piano del dipendente di alto livello è in qualche modo accoppiata al piano specifico di qualsiasi dipendente di livello inferiore.

Se un dirigente di alto livello ha un piano per "migliorare i tempi di consegna" e indica che un dipendente della linea di spedizione deve prendere un caffè e fare stretching ogni mattina, quel piano è altamente accoppiato e ha una bassa coesione. Ma se il piano non menziona alcun dipendente specifico e in realtà richiede semplicemente "un'entità in grado di svolgere il lavoro è pronta a lavorare", allora il piano è liberamente accoppiato e più coerente: i piani non si sovrappongono e possono essere facilmente sostituiti . Gli appaltatori o robot possono facilmente sostituire i dipendenti e il piano di alto livello rimane invariato.

"Livello elevato" nel principio di inversione di dipendenza significa "più importante".


1
Questa è un'analogia straordinariamente buona. Così come, in base al DIC, i componenti di alto livello dipendono ancora in fase di esecuzione da quelli di basso livello, in questa analogia, l'esecuzione dei piani di alto livello dipende dai piani di basso livello. Ma anche, come in DIC sono i piani di basso livello che dipendono in fase di compilazione dai piani di alto livello, è il caso nella tua analogia che la formazione dei piani di basso livello dipende dai piani di alto livello.
Mark Amery, il

0

Vedo che una buona spiegazione è stata data nelle risposte sopra. Tuttavia, voglio fornire alcune semplici spiegazioni con un semplice esempio.

Il principio di inversione di dipendenza consente al programmatore di rimuovere le dipendenze codificate in modo tale che l'applicazione diventi liberamente accoppiabile ed estendibile.

Come raggiungere questo obiettivo: attraverso l'astrazione

Senza inversione di dipendenza:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

Nello snippet di codice precedente, l'oggetto address è hardcoded. Invece se possiamo usare l'inversione di dipendenza e iniettare l'oggetto indirizzo passando attraverso il metodo di costruzione o setter. Vediamo.

Con inversione di dipendenza:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

Inversione di dipendenza: dipende dalle astrazioni, non dalle concrezioni.

Inversione del controllo: principale vs astrazione e come il principale è la colla dei sistemi.

DIP e IoC

Questi sono alcuni buoni post che parlano di questo:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

Diciamo che abbiamo due classi: Engineere Programmer:

L'ingegnere di classe dipende dalla classe del programmatore, come di seguito:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

In questo esempio la classe Engineerha una dipendenza dalla nostra Programmerclasse. Cosa succederà se devo cambiare il Programmer?

Ovviamente anche io devo cambiare Engineer. (Caspita, anche a questo punto OCPviene violato)

Quindi, cosa dobbiamo ripulire questo casino? La risposta è in realtà l'astrazione. Per astrazione, possiamo rimuovere la dipendenza tra queste due classi. Ad esempio, posso creare un Interfaceper la classe Programmer e da ora in poi ogni classe che vuole usare Programmerdeve usarlaInterface , quindi cambiando la classe Programmer, non abbiamo bisogno di cambiare alcuna classe che l'ha usata, a causa dell'astrazione che abbiamo Usato.

Nota: DependencyInjectionci può aiutare a fare DIPe SRPtroppo.


-1

Aggiungendo alla raffica di risposte generalmente buone, vorrei aggiungere un mio piccolo campione per dimostrare le buone e le cattive pratiche. E sì, non sono uno che lancia pietre!

Supponiamo che tu voglia un piccolo programma per convertire una stringa in formato base64 tramite l'I / O della console. Ecco l'approccio ingenuo:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

Il DIP afferma sostanzialmente che i componenti di alto livello non dovrebbero dipendere dall'implementazione di basso livello, dove "livello" è la distanza dall'I / O secondo Robert C. Martin ("Clean Architecture"). Ma come uscire da questa situazione? Semplicemente rendendo l'encoder centrale dipendente solo dalle interfacce senza disturbare il modo in cui sono implementate:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Nota che non è necessario toccare GoodEncoderper cambiare la modalità I / O: quella classe è soddisfatta delle interfacce I / O che conosce; qualsiasi implementazione di basso livello IReadablee IWriteablenon la disturberà mai.


Non penso che questa sia inversione di dipendenza. Dopotutto, non hai invertito alcuna dipendenza qui; il tuo lettore e scrittore non dipendono dal GoodEncodertuo secondo esempio. Per creare un esempio DIP, è necessario introdurre una nozione di ciò che "possiede" le interfacce che hai estratto qui - e, in particolare, metterle nello stesso pacchetto di GoodEncoder mentre le loro implementazioni rimangono all'esterno.
Mark Amery, il

-2

Il principio di inversione di dipendenza (DIP) afferma che

i) I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.

ii) Le astrazioni non dovrebbero mai dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Esempio:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Nota: la classe dovrebbe dipendere da astrazioni come interfaccia o classi astratte, non da dettagli specifici (implementazione dell'interfaccia).

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.