web-dev-qa-db-fra.com

L'injection de dépendances est-elle utile en C ++

C # utilise Injection de dépendance (DI) beaucoup pour avoir un sans perte et testable plateforme. Pour cela, j'ai besoin d'un interface et peut-être d'un DI ou Inversion de contrôle (IoC) ) container pour résoudre mes instances.

Mais comment faites-vous cela en C++? J'ai lu un peu à ce sujet, et il semble que l'injection de dépendances en C++ n'est pas un sujet aussi important qu'en C #. En C++, vous utilisez une référence à un objet - c'est la façon d'utiliser DI en C++, non?

Si ma théorie des références est correcte, y a-t-il quelque chose comme un conteneur où je peux résoudre toutes les références? En C # j'ai un "bad class/bad project/Assembly" qui enregistre toutes mes instances dans un conteneur statique au démarrage du programme. Ensuite, dans chaque classe, je suis en mesure d'instance du conteneur statique et de résoudre une instance spécifique, est-ce possible en C++?

tilisez-vous l'injection de dépendance (ou son nom) en C++? Si oui, comment l'utilisez-vous? Y a-t-il des similitudes avec C #?

22
Marcel Hoffmann

Pour cela, j'ai besoin d'une interface et peut-être d'un conteneur pour résoudre mes instances. Mais comment faites-vous cela en C++?

De la même manière. La différence est que lorsque vous "programmez sur une interface" en C #, vous "programmez sur une classe de base" en C++. De plus, vous disposez d'outils supplémentaires en C++ que vous n'avez pas en C # (par exemple, les modèles basés sur des stratégies implémentent l'injection de dépendance choisie au moment de la compilation).

En C++, vous utilisez une référence à un objet, c'est la façon d'utiliser DI en C++, non?

Non; ce n'est pas la façon d'utiliser DI, c'est a la façon d'utiliser DI en C++.

Considérez également:

  • utiliser un pointeur sur un objet (ou un pointeur intelligent, selon le cas)
  • utiliser un argument de modèle pour une politique (pour un exemple, voir std :: default_delete utilisation dans les pointeurs intelligents)
  • utiliser lambda calcullus avec des foncteurs/prédicats injectés.

En C #, j'ai une "mauvaise classe/mauvais projet/assemblage" qui enregistre toute mon instance dans un conteneur statique au démarrage du programme.

Si je comprends bien, vous définissez toutes vos données dans ce conteneur statique et les utilisez partout dans l'application. Si tel est le cas, vous n'utilisez pas correctement l'injection de dépendance, car cela enfreint la loi de Demeter.

est-ce possible en C++?

Oui, c'est parfaitement possible (mais vous ne devriez pas le faire, car il enfreint la loi de Demeter). Jetez un oeil à boost :: any (cela vous permettra de stocker des objets hétérogènes dans un conteneur, similaire au stockage d'objets par référence object en C #).

Utilisez-vous l'injection de dépendances ou son nom en C++?

Oui (et cela s'appelle l'injection de dépendance :)).

Si oui, comment l'utilisez-vous?

Comme je l'ai décrit ci-dessus (arguments de modèle de politique, foncteurs et prédicats injectés en tant que composants réutilisables, injection d'objets par référence, pointeur intelligent, pointeur ou valeur).

25
utnapistim

L'utilisation de l'injection de dépendances est assez simple en C++. Définissez simplement une interface (une pure classe de base abstraite) que vous utilisez comme référence ou argument pointeur (ou pointeur intelligent) pour le constructeur ou la fonction init de la classe dans laquelle vous souhaitez injecter des dépendances.

Ensuite, dans le test unitaire, injectez un objet factice (une instance d'une classe héritant de la classe d'interface abstraite), et dans du code réel, injectez une instance de la classe réelle (héritant également de la même classe d'interface).

Peasy facile.

16
Erik Alapää

Avec C++ 11 comme limite de projet, j'ai fini par rouler le mien. Je l'ai vaguement basé sur l'API .NET Ninject sans la réflexion ofcourse.

ServiceLocator

Remarque, bien que son appelé ServiceLocator (car il ne fait pas d'injection de dépendance lui-même) si vous utilisez des liaisons de fonction lambda et de préférence des classes ServiceLocator :: Module, vous obtenez Injection (non basé sur la réflexion) et cela fonctionne vraiment très bien (IMO)

#include <iostream>
#include <vector>
#include "ServiceLocator.hpp"

template <class T>
using sptr = std::shared_ptr<T>;

// Some plain interfaces
class IFood {
public:
    virtual std::string name() = 0;
};

class IAnimal {
public:
    virtual void eatFavouriteFood() = 0;
};


// Concrete classes which implement our interfaces, these 2 have no dependancies
class Banana : public IFood {
public:
    std::string name() override {
        return "Banana";
    }
};

class Pizza : public IFood {
public:
    std::string name() override {
        return "Pizza";
    }
};

// Monkey requires a favourite food, note it is not dependant on ServiceLocator
class Monkey : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Monkey(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Monkey eats " << _food->name() << "\n";
    }
};

// Human requires a favourite food, note it is not dependant on ServiceLocator
class Human : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Human(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Human eats " << _food->name() << "\n";
    }
};

/* The SLModule classes are ServiceLocator aware, and they are also intimate with the concrete classes they bind to
   and so know what dependancies are required to create instances */
class FoodSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IFood>("Monkey").to<Banana>([] (SLContext_sptr slc) { 
            return new Banana();
        });
        bind<IFood>("Human").to<Pizza>([] (SLContext_sptr slc) { 
            return new Pizza();
        });
    }
};

class AnimalsSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IAnimal>("Human").to<Human>([] (SLContext_sptr slc) { 
            return new Human(slc->resolve<IFood>("Human"));
        });
        bind<IAnimal>("Monkey").to<Monkey>([] (SLContext_sptr slc) { 
            return new Monkey(slc->resolve<IFood>("Monkey"));
        });
    }
};

int main(int argc, const char * argv[]) {
    auto sl = ServiceLocator::create();

    sl->modules()
        .add<FoodSLModule>()
        .add<AnimalsSLModule>();

    auto slc = sl->getContext();

    std::vector<sptr<IAnimal>> animals;
    slc->resolveAll<IAnimal>(&animals);

    for(auto animal : animals) {
        animal->eatFavouriteFood();
    }

    return 0;
}
8
steve

Oui, l'injection de dépendances est également utile en C++. Il n'y a aucune raison pour que ce ne soit pas le cas, car il ne nécessite pas de langage ou de syntaxe spécifique, mais juste une architecture de classe orientée objet (au moins c'est probablement le cas le plus courant).

Alors qu'en C # il n'y a que des "pointeurs" vers des objets alloués dynamiquement, C++ a plusieurs variantes, comme des variables locales "normales", plusieurs types de pointeurs, des références ... en outre, le concept de sémantique de déplacement est très pertinent à cet égard.

En C++, vous utilisez une référence à un objet, c'est la façon d'utiliser DI en C++, non?

Pas seulement. Vous pouvez utiliser ce que vous voulez tant que vous pouvez passer quelque chose à une méthode de classe et ce quelque chose existera aussi longtemps que l'objet de classe le fera. Les trois possibilités ci-dessus peuvent le faire (chacune avec certaines restrictions)

existe-t-il quelque chose comme un conteneur où puis-je résoudre toutes ces références? En C #, j'ai une "mauvaise classe/mauvais projet/assemblage" qui enregistre toute mon instance dans un conteneur statique

Peut-être que vous manquez le point de l'injection de dépendance. Ce n'est pas la même chose qu'un tas de variables "globales". Mais oui, bien sûr, cela est également possible en C++. Il y a des classes, il y a static, et c'est tout ce qu'il faut.

4
deviantfan

Si ma théorie des références est correcte, y a-t-il quelque chose comme un conteneur où je peux résoudre toutes les références? En C #, j'ai un "mauvaise classe/mauvais projet/assemblage" qui enregistre toutes mes instances dans un conteneur statique au démarrage du programme. Ensuite, dans chaque classe, je suis en mesure d'instance du conteneur statique et de résoudre une instance spécifique, est-ce possible en C++?

Ce n'est pas ainsi que la DI est censée être utilisée, vous ne passez pas votre conteneur à toute votre classe "consommateur". Dans une application bien conçue, vous ne résolvez que très peu le point d'entrée et c'est tout. La plupart du temps, la nécessité d'une "résolution" peut être remplacée par l'utilisation d'une usine qui sera enregistrée puis injectée.

Vous aurez beaucoup de problèmes à tester le code en fonction d'une classe statique. Je recommanderais si vous voulez vraiment injecter votre conteneur dans votre classe client au moins et l'injecter, les dépendances statiques sont un enfer, il serait plus facile de se moquer des tests unitaires.

1
Emmanuel Istace