Sto cercando di capire come l'inversione di dipendenza non porti ad un accoppiamento più stretto nei moduli di livello inferiore e meno riusibilità

4

Sto rifattorizzando un progetto che ho fatto per il mio lavoro e sto cercando di applicare i principi SOLID per rendere l'architettura più pulita. Ho riscontrato un problema con il principio di inversione delle dipendenze che non riesco a risolvere.

Il principio di inversione di dipendenza afferma che i moduli di livello superiore non dovrebbero dipendere da quelli di livello inferiore, ma entrambi dovrebbero dipendere dalle astrazioni. Nel contesto del mio programma, ho una classe App che esegue il programma, e una classe per il sensore a infrarossi MLX90614 che sto usando per prendere oggetti e temperature ambientali. Nella versione iniziale, l'app dipendeva semplicemente direttamente dalla classe di driver MLX90614. Ma questo viola il DIP.

//MLX90614.h
class MLX90614
{
    public:
    readTemperature();
}

//app.h
#include "mlx90614.h"
class App
{
   public:
   //do stuff
   private:
   MLX90614 sensor;
}

Per risolvere il problema, ho deciso di creare un'interfaccia ISensor astratta che potesse essere utilizzata da App e implementata da MLX90614.

//sensor.h
class ISensor
{
    public:
    virtual public readTemperature() = 0;
}

class MLX90614 : public ISensor
{
    //implements ISensor
}

//app.h
#include "sensor.h"
class App
{
    private:
    ISensor *sensor;
}

Questo funziona per rimuovere la dipendenza concreta dall'app, ma trovo alcuni problemi con questo metodo che nessun tutorial su DIP sembra coprire.

Il primo è che ora MLX90614 ha nuove dipendenze introdotte. Ciò significa che se voglio riutilizzare il driver in un'applicazione completamente diversa, che è altamente probabile, dovrò trascinare l'intero sistema di interfaccia insieme ad esso. Questo sembra un po 'il problema di "voler una banana ma ottenere il gorilla e la foresta con essa". Se dovessi riutilizzare questo codice e rilasciarlo come libreria separata al pubblico, non ci sarebbe alcun uso per l'interfaccia ISensor specifica dell'applicazione, che mi sembra un pessimo design del pacchetto.

Per peggiorare le cose, molti tutorial specificano specificamente che per aderire completamente al DIP, l'interfaccia deve essere impacchettata con il componente di livello superiore (questo soddisfa la seconda regola). Questo rende le cose molto più complicate e complicate, dato che ora MLX90614 è strettamente associato al modulo App di livello superiore, e per riutilizzare il driver devo trascinare non solo l'interfaccia, ma l'intera intera applicazione insieme! Questo sembra un incubo di programmazione!

//mlx90614.h
#include "app.h"
class MLX90614 : public ISensor
{
    //Implements ISensor, but now is completely dependent on the App module,
    //which we definitely don't want.
}

//app.h
class ISensor
{
    //do stuff
}

class App
{
    private:
    ISensor *sensor;
}

Per questo motivo, non riesco a capire in che modo l'inversione di dipendenza fa più bene del danno al mio sistema. Se voglio che i moduli di livello inferiore siano altamente riutilizzabili, allora sembra strano renderli dipendenti da un intero sistema di livello superiore che è completamente irrilevante per la loro implementazione. E considerando come nella maggior parte dei sistemi che ho programmato le librerie di livello inferiore sono sempre molto più riutilizzabili rispetto alle implementazioni di livello superiore, non riesco a vedere come l'inversione di dipendenza sia utile.

    
posta Ryan Mullin 29.03.2018 - 08:54
fonte

2 risposte

13

I fail to see how dependency inversion is useful at all.

Questo perché non hai ancora utilizzato per nulla.

Ti stai lamentando del fatto che DIP funziona, aggiunge complessità e non fa nulla per te. Tutto ciò è vero al momento. Quello che non riesci a capire è che DIP non si tratta di ora. Riguarda il cambiamento.

Si prevede che l'utilizzo di DIP significherà che non è possibile riutilizzare il codice del sensore senza trascinare elementi con esso. Qui hai il rapporto indietro.

Non si crea l'interfaccia ISensor per MLX90614. Lo crei per l'app che dipende dall'avere un sensore e non gli interessa se è MLX90614 o qualcos'altro. Dove prima la tua app era a conoscenza di MLX90614 direttamente ora sta semplicemente pubblicando un'interfaccia che spiega in dettaglio ciò di cui ha bisogno dal sensore. Qualunque cosa sia. È una buona cosa. L'app potrebbe vivere più a lungo di MLX90614.

Quando arriva il momento di usare MLX90614 in un'altra app, dovresti usare un'interfaccia diversa. Uno che mostra di cosa ha bisogno l'altra app. Non tutto ciò che fa MLX90614. Questo è il principio di segregazione dell'interfaccia.

Per qualche motivo insisti sul fatto che MLX90614 deve poter essere spostato da un'app all'altra non toccata dall'esperienza. Forse stai pensando di metterlo in una biblioteca. In tal caso, prenderemo in considerazione il Pattern adattatori per gli oggetti per evitare di ottenere la sua testardaggine su tutta la mia app.

Fai quello che ti piace con la tua banana. Ma il gorilla è quello che decide cosa vuole mangiare e come sbucciarlo.

    
risposta data 29.03.2018 - 09:38
fonte
1

Se vuoi che ISensor sia implementato da più sensori e consumati da più applicazioni, allora hai qualcosa di simile a questo:

A questo punto, hai due scelte:

1) Package ISensor insieme a Sensor 1 e Sensor 2. Tutte le applicazioni che utilizzano uno o più sensori ora fanno riferimento a quel pacchetto.

2) Package ISensor da solo. Ora tutte le applicazioni e tutti i pacchetti di sensori fanno riferimento al pacchetto per ISensor. Le applicazioni devono anche fare riferimento a qualsiasi implementazione concreta che vogliono utilizzare.

    
risposta data 29.03.2018 - 22:05
fonte