In che modo l'iniezione della dipendenza è diversa dall'utilizzo semplice dell'interfaccia?

2

Durante il tentativo di risolvere un problema, spiegato su Forum StackOverflow , qualcuno mi ha consigliato di utilizzare l'iniezione di dipendenza. Per motivi personali, nel momento in cui una persona mi parla dell'uso di un modello di design, comincio sempre a pensare a costruzioni molto difficili. Dopo aver riflettuto e investigato, ho inventato la seguente costruzione (pseudo-codice):

File di intestazione generale

interface ILogger {
public:
  void writeMsg(std::string);
};

class Application_Logger : ILogger {
public:
  void writeMsg(std::string output) {
    std::printf(stringutils::format("Application : %s", output);
  }
};

class Test_Logger : ILogger {
public:
  void writeMsg(std::string output) {
    std::printf(stringutils::format("Test : %s", output);
  }
};

File di intestazione Application.h:

ILogger logger = nullptr;

File di intestazione Unit_test.h:

ILogger logger = nullptr;

Application_startup.cpp:

if (logger == nullptr)
  ILogger logger = Application_Logger();

Unit_testing_startup.cpp:

if (logger == nullptr)
  ILogger logger = Test_Logger();

Common_used.cpp:

logger.writeMsg("<information>");
logger.writeMsg("<more information>");

Output dell'applicazione

Application : <information>
Application : <more information>

Uscita test unità

Test : <information>
Test : <more information>

Non ho idea se funzioni o meno, ma credo che lo faccia (assumendo che sia possibile lanciare un pezzo di codice, abilitando a riempire il puntatore dell'interfaccia).
A mio parere, questa non è una costruzione speciale, ma l'uso di base delle interfacce, non degno di essere chiamato modello di progettazione. Sono corretto e, in caso contrario, cosa deve essere aggiunto / modificato al fine di creare un modello di iniezione diretta al di fuori di questo?

Dopo alcuni primi commenti, sto iniziando a capire perché non si tratta di un'iniezione diretta, quindi di seguito un altro esempio, cercando di realizzare un'iniezione diretta del costruttore molto semplice (potendo semplicemente passare da una interfaccia all'altra di ILogger):

File di intestazione generale

interface ILogger {
public:
  void writeMsg(std::string;int);
};

class Simple_Logger : ILogger {
public:
  void writeMsg(std::string output;int severity) {
    std::printf(stringutils::format("[%d] : %s", severity, output));
  }
};

class Detailed_Logger : ILogger {
public:
  void writeMsg(std::string output;int severity) {
    if (severity == 0) {
    std::printf(stringutils::format("Very important : %s", output));
    } else {
    std::printf(stringutils::format("[%d] : %s", severity, output));
    }
  }
};

File di intestazione Application.h:

ILogger logger = nullptr;

Application_startup.cpp (basato su args ):

if (logger == nullptr) {
  if args == "Simple"
  ILogger logger = Simple_Logger();
  else : ILogger logger = Detailed_Logger();
}

Se questo è corretto, significa che DI significa che l'elaborazione dell'applicazione è basata su interfacce e che i dati esterni (argomenti, contenuto del file di configurazione, input interattivo, ...) decidono quale implementazione è scelta per tali interfacce . Ho ragione stavolta? (Nella pagina di Wikipedia, non è chiaro da dove proviene il citato service

    
posta Dominique 01.02.2018 - 10:23
fonte

3 risposte

1

Sono d'accordo con le altre risposte, ma sembra che ci sia ancora un po 'di confusione. Diamo un'occhiata a un esempio.

class Logger
{
public:
    virtual void writeMessage(std::string&) = 0;
}

class AppLogger : public Logger
{
public:
    virtual void writeMessage(std::string &str) { /* ... */ }
}

class TestLogger : public Logger
{
public:
    virtual void writeMessage(std::string &str) { /* ... */ }
}

class DataProcessor
{
public:
    DataProcessor(Logger &logger)   // dependency is injected via constructor
    {
        // The DataProcessor does NOT know anything about AppLogger or TestLogger.
        // In particular, the DataProcessor does NOT create any loggers!!
        // This is the key point of dependency injection - the dependencies are
        // provided from the outside of the class.

        this->logger = logger;      // save it for later
    }

    void ProcessSomeData()
    {   
        std::string msg("Processing some data");
        logger.writeMessage(msg);   // use the injected dependency
        // ...
    }

private:
    Logger logger;
}

void someFunctionSomewhere()
{
    AppLogger logger;
    DataProcessor processor(logger);    // inject the dependency here
    processor.ProcessSomeData();
}

È importante che DataProcessor non crei o faccia affidamento su alcuna specifica implementazione di Logger , ma solo sull'interfaccia. Ciò consente di iniettare qualsiasi implementazione concreta di Logger durante il runtime.

Nota che ho mostrato l'iniezione del costruttore. Esistono altri modi per iniettare le dipendenze, ma l'idea generale è che le classi che DataProcessor usa (cioè le sue dipendenze) sono non create da DataProcessor ma iniettate dall'esterno.

    
risposta data 01.02.2018 - 13:16
fonte
9

L'iniezione di dipendenza non riguarda la programmazione per interfacce piuttosto che le implementazioni. Questo è un obiettivo degno, ma diverso.

No, DI parla di essere in grado di scambiare i provider di interfaccia senza ricompilare il mondo . Quando il tuo codice contiene una chiamata al costruttore concreto come Application_Logger() , non puoi più passare dall'uso di questa particolare classe senza ricompilare. Se l'avessi ottenuto in un altro modo - da una Factory, attraverso un registro o tramite DI - allora sarebbe possibile cambiare un file di configurazione e scambiare logger. Così com'è, dovrai ricompilare e ridistribuire la tua applicazione per modificare dettagli di configurazione così piccoli. Se gestisci sistemi software di grandi dimensioni, questo è fastidioso e costoso.

(Questo è il motivo per cui i DI e le tecniche associate diventano più importanti, più grandi e complessi sono i tuoi sistemi. Sfortunatamente, gli esempi che rientrano in una domanda di Stackexchange non sono mai argomenti convincenti per l'utilizzo di concetti a livello aziendale.)

    
risposta data 01.02.2018 - 10:32
fonte
1

L'iniezione di dipendenza è solo un piccolo passo sulla programmazione delle interfacce. Nello specifico, si tratta di fornire oggetti con le dipendenze di cui hanno bisogno (sotto forma di implementazione di interfaccia) esternamente (ad esempio passando un puntatore al costruttore).

Quindi usa le variabili membro invece dei globali e hai DI.

Potresti non pensare che questo sia degno di essere un modello di progettazione, ma la storia dello sviluppo del software aziendale non è d'accordo. Questo essere un modello è importante per distinguerlo da schemi precedenti come Service Locator, che era dominante nel mondo JavaEE iniziale. Service Locator sta anche programmando le interfacce, ma il metodo per ottenere un'implementazione dell'interfaccia è diverso. I principali vantaggi di DI rispetto a SL sono che è più semplice impostare i test e che le classi sono meno strettamente collegate al framework che si sta utilizzando.

O per dirla in altro modo: l'utilizzo di un'interfaccia semplice significa semplicemente l'utilizzo di interfacce. DI riguarda il modo in cui ottieni l'interfaccia.

    
risposta data 01.02.2018 - 11:36
fonte

Leggi altre domande sui tag