Iniezione delle dipendenze (DI) nelle applicazioni c ++

8

Sto giocando con l'iniezione di dipendenza, ma non sono sicuro che lo stia facendo bene. Soprattutto, non sono sicuro di quale dovrebbe essere il modo corretto per creare classi con dipendenze iniettate.

Dire che ho una classe A che crea la classe B. La classe B dipende dalla classe C e la classe C dipende dalla classe D. Chi dovrebbe essere responsabile della creazione della classe D?

  1. Potrebbe essere la classe A. Tuttavia, in un sistema di grandi dimensioni, la classe A potrebbe finire per creare e assemblare un numero molto grande di oggetti.

  2. Una classe di builder separata che creerà D, C e B. A utilizzerà questa classe builder.

  3. Un'altra opzione.

Inoltre, ho letto molto sui contenitori DI. Tuttavia, sembra che non ci siano framework principali per C ++. Inoltre, se ho capito bene, DI può essere eseguito bene anche senza contenitori. Sono corretto?

    
posta Erik Sapir 26.03.2014 - 20:29
fonte

3 risposte

6

Say I have a class A that creates class B. Class B depends on class C and class C depends on class D. Who should be responsible for creating class D?

Stai saltando dei passi. Si consideri una serie di convenzioni ottimizzate per l'accoppiamento lento e la sicurezza delle eccezioni. Le regole vanno così:

  • R1: se A contiene una B, quindi il costruttore di A riceve una B completamente costruita (cioè non "le dipendenze di costruzione di B"). Allo stesso modo, se la costruzione di B richiede una C, riceverà una dipendenza da C, non da C.

  • R2: se è necessaria una catena completa di oggetti per costruire un oggetto, la costruzione concatenata viene estratta / automatizzata all'interno di un factory (funzione o classe).

Codice (std :: sposta le chiamate omesse per semplicità):

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(C c) {return B{c}; };

In tale sistema, "chi crea D" è irrilevante, perché quando chiami make_b, hai bisogno di una C, non di una D.

Codice cliente:

A a; // factory instance

// construct a B instance:
D d;
C c {d};
B = a.make_b(c);

Qui D viene creato dal codice client. Naturalmente, se questo codice viene ripetuto più volte, sei libero di estrarlo in una funzione (vedi R2 sopra):

B make_b_from_d(D& d) // you should probably inject A instance here as well
{
    C c {d};
    A a;
    return a.make_b(c);
}

C'è una tendenza naturale a saltare la definizione di make_b (ignora R1 ) e scrivere il codice direttamente in questo modo:

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly

In questo caso, hai i seguenti problemi:

  • hai un codice monolitico; Se si arriva a una situazione nel codice client in cui è necessario creare una B da una C esistente, non è possibile utilizzare make_b. Dovrai scrivere un nuovo factory, o la definizione di make_b, e tutto il codice client usando il vecchio make_b.

  • La tua visione delle dipendenze è confusa quando guardi la fonte: ora, osservando la fonte, pensi di aver bisogno di un'istanza di tipo D, quando in realtà potresti aver bisogno di una C

Esempio:

void sub_optimal_solution(C& existent_c) {
    // you cannot create a B here using existent_C, because your A::make_b
    // takes a D parameter; B's construction doesn't actually need a D
    // but you cannot see that at all if you just have:
    // struct A { B make_b(D d); };
}
  • L'omissione di struct A { B make_b(C c); } aumenterà notevolmente l'accoppiamento: ora A ha bisogno di conoscere le definizioni di B e C (invece di solo C). Hai anche restrizioni sul qualsiasi codice client che utilizza A, B, C e D, imposto sul tuo progetto perché hai saltato un passaggio nella definizione di un metodo factory ( R1 ) .

TLDR: In breve, non passare la dipendenza più esterna a una fabbrica, ma i più vicini. Questo rende il tuo codice robusto, facilmente modificabile e rende la domanda che hai posto ("chi crea D") in una domanda irrilevante per l'implementazione di make_b (perché make_b non riceve più D ma una dipendenza più immediata - C - e questo è iniettato come parametro di make_b).

    
risposta data 27.03.2014 - 16:58
fonte
3

Esiste un framework DI per C ++ (ancora in fase di sviluppo AFAIK): Boost.DI .

Ci sono alcuni commenti utili sul framework su reddit.

    
risposta data 22.01.2015 - 17:16
fonte
0

Who should be responsible for creating class D?

Ogni volta che viene visualizzata una domanda simile, indica una dipendenza da iniettare . L'idea è quella di "delegare" la responsabilità al di fuori dell'oggetto che sembra avere un problema a capire come gestirlo.

Usando il tuo esempio, dal momento che la classe A non sembra possedere una "conoscenza" sufficiente per capire come creare D, tu inverti il controllo e esponilo come una dipendenza necessaria per la classe A, richiedendo un fabbrica che sa come creare istanze di classe D.

    
risposta data 26.03.2014 - 20:41
fonte

Leggi altre domande sui tag