it-swarm.it

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

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

163
Phillip Wells

Controlla questo documento: The Dependency Inversion Principle .

In pratica dice:

  • I moduli di alto livello non dovrebbero dipendere dai 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 perché è importante, in breve: i cambiamenti sono rischiosi e, in base a un concetto anziché a un'implementazione, si riduce la necessità di modificare i 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, ad esempio, una struttura di registrazione, il modo in cui lo userete dovrebbe essere relativamente stabile nel tempo. Se è possibile estrarre un'interfaccia che rappresenta il concetto di registrazione, questa interfaccia dovrebbe essere molto più stabile nel tempo rispetto all'implementazione e i siti di chiamata dovrebbero essere molto meno interessati dalle modifiche che è possibile apportare mantenendo o estendendo tale meccanismo di registrazione.

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

102
Carl Seleborg

Quando progettiamo applicazioni software possiamo considerare le classi di basso livello le classi che implementano le 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 aziendali, ...).

Gli ultimi si basano sulle classi di basso livello. Un modo naturale per implementare tali strutture sarebbe quello di scrivere classi di basso livello e una volta che avremo loro di 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" la nozione convenzionale che 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. Così facendo i moduli di livello inferiore dipendono da moduli di livello superiore.

10
nikhil.singhal

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

Architettura tradizionale a strati

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Devi capire strato, 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 a livello di libreria o 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 di pensiero 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 di alto livello del modulo sarebbe dove l'azione è invocata e il livello basso in cui viene eseguita l'azione.

Una conclusione ragionevole da trarre da questo principio è che non dovrebbe esserci alcuna dipendenza tra le concrezioni, ma deve esserci una dipendenza da un'astrazione. Ma secondo l'approccio che adottiamo, possiamo applicare erroneamente dipendenza dipendente 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 definisce l'astrazione.

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

}

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

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

Anche se dipendiamo da una dipendenza da astrazione tra business e accesso ai dati rimane la stessa.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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.

Definire innanzitutto cosa sia il livello del dominio e l'astrazione della sua comunicazione è definita persistenza.

// Domain.dll
public interface IProductRepository;

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

Dopo che lo strato di persistenza dipende dal dominio, invertire ora se è stata definita una dipendenza.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Approfondire il principio

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

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

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

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

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

Architetture

Esistono architetture in cui l'inversione di dipendenza è fondamentale per la sua definizione. In tutti i domini è il più importante e sono le astrazioni che indicano il protocollo di comunicazione tra il dominio e il resto dei pacchetti o le librerie sono definite.

Architettura pulita

In 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. I livelli esterni sono considerati strumenti instabili, quindi evita di dipendere da essi.

Architettura esagonale

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

9
xurxodev

Per me, il principio di inversione delle dipendenze, come descritto nell'articolo ufficiale , è in realtà un tentativo errato di aumentare la riusabilità di moduli che sono intrinsecamente meno riutilizzabili, nonché un modo per risolvere un problema nel linguaggio C++.

Il problema in C++ è che i file di intestazione contengono in genere 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à dall'effettivo implementazione dettagli di quel modulo. E questo, ovviamente, non è una buona cosa. Ma questo non è un problema nei più moderni linguaggi comunemente usati oggi.

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

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 eseguita solo se la componente A sarà davvero utile per il riutilizzo in diverse applicazioni o contesti. Se questo non è il caso, quindi l'applicazione di DIP sarebbe una cattiva progettazione.

9
Rogério

Fondamentalmente dice:

La classe dovrebbe dipendere dalle astrazioni (per esempio interfaccia, classi astratte), non da dettagli specifici (implementazioni).

8
martin.ra

Un modo molto più chiaro per enunciare il principio di inversione delle dipendenze è:

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

I.e, invece di implementare la classe Logic come di solito fanno le persone:

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

dovresti fare qualcosa come:

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

Data e DataFromDependency dovrebbero vivere nello stesso modulo di Logic, non con Dependency.

Perché questo?

  1. I due moduli logici di business sono ora disaccoppiati. Quando Dependency cambia, non è necessario modificare Logic
  2. Capire cosa Logic fa è un compito molto più semplice: funziona solo su ciò che sembra un ADT.
  3. Logic può ora essere più facilmente testato. Ora è possibile istanziare direttamente Data con dati falsi e inoltrarlo. Nessuna necessità di mock o complessi scaffold di test.
5
mattvonb

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

Il motivo DIP è importante perché garantisce il principio OO "design senza accoppiamento".

Gli oggetti nel software NON devono entrare in una gerarchia in cui alcuni oggetti sono quelli di livello superiore, dipendenti da oggetti di basso livello. Le modifiche agli oggetti di basso livello verranno quindi propagate 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.

5
Hace

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

Esempio di pseudocodice che utilizza 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 hai alcuna dipendenza da un framework .__ centrale, quindi questo può essere modificato se Desiderato.
  • Poiché gli oggetti sono creati mediante l'iniezione, preferibilmente usando le interfacce , È facile creare test unit Che sostituiscano le dipendenze con le versioni Mock.
  • Disaccoppiamento del codice.
5
Staale

Il punto di inversione della dipendenza è di rendere il software riutilizzabile.

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

Il modo in cui questo è più comunemente raggiunto è attraverso un'inversione del contenitore di controllo (IoC) come Spring in Java. In questo modello, le proprietà degli oggetti vengono impostate tramite una configurazione XML anziché gli oggetti che 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 quelli se vuoi usarlo in un'altra applicazione. Ora immagina questo ...

public class MyClass
{
  public IService myService;
}

Ora, MyClass si basa su un'unica interfaccia, l'interfaccia IService. Faremo in modo che il contenitore IoC imposti il ​​valore di tale variabile.

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

Ancora meglio, non è necessario trascinare le dipendenze di MyService e le dipendenze di tali dipendenze e ... beh, si ottiene l'idea.

1
Marc Hughes

L'inversione dei contenitori di controllo e il modello di iniezione delle dipendenze di Martin Fowler è anche una buona lettura. Ho trovato Head First Design Patterns un libro fantastico per la mia prima incursione nell'apprendimento di DI e altri modelli.

1
Chris Canal

Penso di avere un esempio molto migliore (più intuitivo).

  • Immagina un sistema (webapp) con gestione dipendenti e contatti (due schermi).
  • Non sono esattamente correlati, quindi li vuoi ciascuno nel proprio modulo/cartella

Quindi avresti un punto d'ingresso "principale" che dovrebbe conoscere sia il modulo di gestione dei dipendenti che il modulo di gestione dei contatti, e dovrebbe fornire collegamenti nella navigazione e accettare richieste API, ecc. In altre parole, il il modulo principale dipenderebbe da questi due: sarebbe a conoscenza dei loro controllori, rotte e collegamenti che devono essere resi nella navigazione (condivisa).

Esempio Node.js

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Inoltre, si prega di notare ci sono casi (quelli semplici) quando questo è completamente bene.


Quindi nel tempo arriverà a un punto in cui non è così semplice aggiungere nuovi moduli. Devi ricordarti di registrare API, navigazione, forse permessi , e questo main.js diventa sempre più grande.

Ed è qui che entra in gioco l'inversione di dipendenza. Invece che il tuo modulo principale dipende da tutti gli altri moduli, dovrai introdurre alcuni "core" e far sì che ogni modulo si registri da solo.

Quindi in questo caso si tratta di avere un'idea di alcuni ApplicationModule, che possono sottomettersi a molti servizi (rotte, navigazione, permessi) e il modulo principale può rimanere semplice (basta importare il modulo e lasciarlo installare)

In altre parole, si tratta di fare architettura collegabile. Questo è un lavoro extra e un codice che dovrai scrivere/leggere e mantenere, quindi non dovresti farlo in anticipo, ma piuttosto quando hai questo tipo di odore.

Ciò che è particolarmente interessante è che puoi rendere qualsiasi cosa un plug-in, anche il livello di persistenza, che potrebbe valere la pena se devi supportare molte implementazioni di persistenza, ma di solito non è così. Vedi l'altra risposta per l'immagine con architettura esagonale, è fantastica per l'illustrazione - c'è un core e tutto il resto è essenzialmente un plugin.

0
Kamil Tomšík

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

Inversione del controllo: Main vs Abstraction e come Main è la colla dei sistemi.

DIP and 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/

In aggiunta ad altre risposte ....

Permettetemi di presentare un esempio per primo ..

Lasciate che ci sia un Hotel che chieda un generatore di cibo per i suoi rifornimenti. L'Hotel dà il nome del cibo (ad esempio pollo) al Food Generator e il Generatore restituisce il cibo richiesto all'Hotel. Ma l'Hotel non si cura del tipo di cibo che riceve e serve. Quindi il generatore fornisce agli alimenti un'etichetta di "cibo" per l'hotel.

Questa implementazione è in Java

FactoryClass con un metodo di fabbrica. Il Food Generator

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Una classe Abstract/Interface

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


Il pollo implementa il cibo (una classe concreta)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Il pesce implementa il cibo (una classe concreta)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Finalmente

L'albergo

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Come puoi vedere L'Hotel non sa se si tratta di un oggetto di pollo o di un pesce. Sa solo che è un oggetto alimentare che l'Hotel dipende dalla Food Class.

Inoltre noteresti che la classe Fish and Chicken implementa la Food Class e non è direttamente collegata all'Hotel. Il pollo e il pesce dipendono anche dalla classe alimentare.

Il che implica che il componente di alto livello (Hotel) e il componente di basso livello (pesce e pollo) dipendono entrambi da un'astrazione (cibo).

Questo è chiamato Dependency Inversion. 

0
Revolver

Detto questo, il principio di inversione delle dipendenze (DIP) 

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 l'interfaccia o classi astratte, non dettagli specifici (implementazione dell'interfaccia).

0
Rejwanul Reja