Полезна ли инъекция зависимостей в C ++

C # часто использует внедрение зависимостей (DI), чтобы иметь без потерь и тестируемый < / strong> платформа. Для этого мне нужен interface и, возможно, контейнер DI или Inversion of Control (IoC) для разрешения моих экземпляров.

Но как это сделать в C ++? Я немного читал об этом, и мне кажется, что внедрение зависимостей в C ++ не такая большая тема, как в C #. В C ++ вы используете ссылку на объект - это способ использования DI в C ++, верно?

Если моя теория со ссылками верна, есть ли что-то вроде контейнера, в котором я могу разрешить все ссылки? В C # у меня есть "bad class/bad project/assembly", который регистрирует все мои экземпляры в статическом контейнере при запуске программы. Затем в каждом классе я могу создать экземпляр статического контейнера и разрешить конкретный экземпляр, возможно ли это в C ++?

Используете ли вы внедрение зависимостей (или как там это называется) в C ++? Если да, то как вы это используете? Есть ли сходство с C #?


person Marcel Hoffmann    schedule 31.03.2015    source источник


Ответы (6)


Для этого мне нужен интерфейс и, возможно, контейнер для разрешения моих экземпляров. Но как это сделать в C ++?

Точно так же. Разница в том, что если вы «программируете на интерфейс» на C #, вы «программируете на базовый класс» на C ++. Кроме того, у вас есть дополнительные инструменты в C ++, которых нет в C # (например, шаблоны на основе политик реализуют внедрение зависимостей, выбираемое во время компиляции).

В C ++ вы используете ссылку на объект, это способ использования DI в C ++, верно?

Нет; это не способ использования DI, это способ использования DI в C ++.

Также учтите:

  • использовать указатель на объект (или умный указатель, в зависимости от случая)
  • использовать аргумент шаблона для политики (например, см. использование std :: default_delete в интеллектуальных указателях)
  • используйте lambda calcullus с введенными функторами / предикатами.

В C # у меня есть «плохой класс / плохой проект / сборка», которые регистрируют все мои экземпляры в статическом контейнере при запуске программы.

Если я правильно понимаю, вы устанавливаете все свои данные в этот статический контейнер и используете его во всем приложении. Если это так, то вы неправильно используете внедрение зависимостей, потому что это нарушает закон Деметры.

возможно ли это в C ++?

Да, это вполне возможно (но не стоит этого делать, так как это нарушает закон Деметры). Взгляните на boost :: any (это позволит вам хранить разнородные объекты в контейнере, аналогично хранению объектов по object ссылке в C #).

Вы используете инъекцию зависимостей или как это называется в C ++?

Да (и это называется инъекцией зависимостей :)).

Если да, то как вы это используете?

Как я описал выше (аргументы шаблона политики, внедренные функторы и предикаты как повторно используемые компоненты, внедрение объектов по ссылке, интеллектуальный указатель указателя или значение).

person utnapistim    schedule 31.03.2015
comment
Я думаю, вы меня не поняли. Под плохим проектом я подразумеваю проект, в котором все ссылки ссылаются на разные слои. Этот плохой проект регистрирует все зависимости в преобразователе зависимостей. Взгляните на составной компонент. - person Marcel Hoffmann; 31.03.2015
comment
Вы предпочитаете использовать ди-контейнер? Например, если у меня четыре класса: класс 2 вводится в класс 1, класс 3 вводится в класс 2, класс 4 вводится в класс 3. В этом случае вам очень поможет ди-контейнер. Есть ли у вас какие-нибудь советы по поводу хорошего ди-фреймворка? - person Marcel Hoffmann; 31.03.2015
comment
@utnapistim Хотите впервые попробовать DI на C ++ (я использую Dagger 2 для Java). Какой из этих двух вы порекомендуете? github.com/google/fruit и github.com/boost-experimental/di - person Ehtesham Hasan; 15.04.2018
comment
Закон Деметры: en.wikipedia.org/wiki/Law_of_Demeter, также известный как принцип наименьшего знания - person TankorSmash; 13.02.2019

Использование внедрения зависимостей в C ++ довольно просто. Просто определите интерфейс (чистый абстрактный базовый класс), который вы используете в качестве аргумента ссылки или указателя (или интеллектуального указателя) для конструктора или функции инициализации класса, в который вы хотите внедрить зависимость.

Затем в модульном тесте введите фиктивный объект (экземпляр класса, наследующий от абстрактного класса интерфейса), а в реальном коде введите экземпляр реального класса (также наследующий от того же класса интерфейса).

Очень просто.

person Erik Alapää    schedule 31.03.2015
comment
Будет ли в вашем случае добавление фабрики методов в класс (который создаст новый экземпляр, внедряющий реальные зависимости), считаться хорошей практикой? - person Flavien Volken; 05.04.2017
comment
@FlavienVolken Я не совсем уверен, что вам нужно, но мне не нужна была такая фабрика методов, когда я делал серьезную TDD (Test Driven Development / Design) с использованием Google Test / Mock или CPPUnit на C ++. - person Erik Alapää; 05.04.2017
comment
Я имею в виду, что это скучно создавать объект, передающий 3-4 зависимости, чтобы затем использовать его (исключение для модульного тестирования, где это именно то, что мы ожидаем). Вы бы порекомендовали использовать фабричный метод, такой как createMyObject (), который сам будет создавать экземпляр нового объекта со всеми реальными зависимостями? Таким образом, люди будут только создавать экземпляры объекта, используя фабрику, сохраняя открытую дверь для внедрения пользовательских зависимостей. - person Flavien Volken; 06.04.2017
comment
Если заводской fcn помогает, непременно добавьте. - person Erik Alapää; 06.04.2017

С С ++ 11 в качестве ограничения проекта я закончил свой собственный. Я вольно основал его на .NET Ninject API, конечно, без Reflection.

ServiceLocator

Обратите внимание, хотя он называется ServiceLocator (поскольку он сам не выполняет Dependancy Injection), если вы используете привязки лямбда-функций и, предпочтительно, классы ServiceLocator :: Module, вы получаете Injection (не на основе отражения), и он работает очень хорошо (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;
}
person steve    schedule 20.05.2017
comment
Блин, эта штука сексуальна. Спасибо, что сделали и выпустили. - person Alex; 16.01.2019
comment
Приятно, хотя многие говорят, что локатор услуг - это анти-шаблон - person johnildergleidisson; 01.05.2019
comment
@johnildergleidisson да, мне действительно следует переименовать проект, поскольку при правильном использовании (с SLModule) он не является локатором службы, SLModule тесно связан со своими конкретными классами и поэтому знает, какие зависимости им нужны, и запрашивает эти зависимости из SLContext - его инъекция зависимостей но без отражения времени выполнения для автоматического определения зависимостей - ИМО, он работает очень хорошо, последний проект, в котором я его использовал, был абсолютным удовольствием для улучшения и развития из-за того, как это работало - person steve; 23.05.2019
comment
В чем преимущество использования этого по сравнению с простым созданием экземпляров каждого класса в main? В вашем коде преобразователь не может сказать вам, если вы забыли создать экземпляр одного из IFoods. Вы не узнаете до времени выполнения. Если бы вы использовали ссылки и простой конструктор, вы бы статически знали, что все подключено правильно ... - person masterxilo; 14.12.2020
comment
@masterxilo Независимо от того, какой DI вы используете на любом данном языке, компилятор никогда не скажет вам, если вы забыли зарегистрировать конкретную реализацию для интерфейса, это всегда проблема времени выполнения. Компилятор выдаст ошибку об отсутствии зависимостей для любого конкретного класса - это то, что делают библиотеки DI и что достигается с помощью SLModules. Я использовал файлы конфигурации для включения / выключения SLModules, поэтому у меня было IGPSDevice, SerialPortGPSDevice в одном SLModule, который разговаривал с реальным GPS через последовательный порт, и SimulatedGPSDevice в другом SLModule, который предоставил REST API для модульного тестирования / разработки, командная строка аргументы для управления - person steve; 05.01.2021

Да, внедрение зависимостей также полезно в C ++. Нет причин, по которым этого не должно быть, потому что для этого не требуется конкретный язык или синтаксис, а требуется только объектно-ориентированная архитектура классов (по крайней мере, это, вероятно, наиболее распространенный случай).

В то время как в C # есть только «указатели» на динамически выделяемые объекты, в C ++ есть несколько вариантов, таких как «нормальные» локальные переменные, несколько типов указателей, ссылки ... Кроме того, здесь очень актуальна концепция семантики перемещения.

В C ++ вы используете ссылку на объект, это способ использования DI в C ++, верно?

Не только. Вы можете использовать все, что хотите, до тех пор, пока вы можете передать что-то методу класса, и это что-то будет существовать, пока существует объект класса. Это могут сделать все три вышеперечисленных возможности (каждая с определенными ограничениями).

есть ли что-то вроде контейнера, в котором я могу разрешить все эти ссылки? В C # у меня есть «плохой класс / плохой проект / сборка», которые регистрируют все мои экземпляры в статическом контейнере.

Возможно, вы упускаете из виду точку внедрения зависимостей. Это не то же самое, что набор «глобальных» переменных. Но да, конечно, это возможно и в C ++. Есть классы, есть static, и это все, что нужно.

person deviantfan    schedule 31.03.2015

Если моя теория со ссылками верна, есть ли что-то вроде контейнера, в котором я могу разрешить все ссылки? В C # у меня есть «плохой класс / плохой проект / сборка», который регистрирует все мои экземпляры в статическом контейнере при запуске программы. Затем в каждом классе я могу создать экземпляр статического контейнера и разрешить конкретный экземпляр, возможно ли это в C ++?

Это не то, как предполагается использовать DI, вы не передаете свой контейнер всему своему «потребительскому» классу. В хорошо спроектированном приложении вы просто делаете несколько разрешений в точке входа, и все. В большинстве случаев потребность в «разрешении» может быть заменена использованием фабрики, которая будет зарегистрирована, а затем введена.

В зависимости от статического класса у вас будет много проблем с кодом тестирования. Я бы порекомендовал, если вы действительно хотите внедрить свой контейнер в свой клиентский класс, чтобы хотя бы экземпляр и внедрить его, статические зависимости - это ад, было бы проще имитировать для модульного тестирования.

person Emmanuel Istace    schedule 25.04.2018

Это старый пост, но я написал образец реализации контейнера di на C ++. может тебе интересно.

https://github.com/dirkwinkhaus/widic-dicontainer

person dirkwinkhaus    schedule 02.07.2021
comment
Вместо того, чтобы просто добавлять ссылку на внешнюю ссылку, объясните проблему, которая ее вызывает, а затем предложите решение. - person sidverma; 02.07.2021