LINUX.ORG.RU

IoC в С++

 ,


1

6

.. не Dependency Injection, а именно Inversion of Control я смотрю в плюсах не популярен. В мире Java без этого тебя сначала засмеют, а потом когда поймут что ты не унимаешься - то изобьют.

Что я подразумеваю под IoC

  • Есть контейнер, в которым по определенным ключам (типы, строки) регистрируются классы
  • Контейнер обязан уметь создавать инстансы каждого из классов, если надо подставляя ему или в конструктор или как-то по другому его зависимости
  • Группы регистраций классов можно обьединять в модули, которые просто устанавливаются подключением к основном контейнеру. Они тогда предоставлют или требуют другие класса для своей работы. По сути как паззл.
  • В тестах можно заменять целы модули указывая «запусти мне весь контейнер, но пожалуйста замени MyClass на MyMockClass»

Пример на псевдокоде который вроде бы как С++

class IB {
public:
  int value() = 0;
};

class B : public IB {
public:
   int value() {
      return 1;
   }
};

class A {
   A(std::shared_ptr<IB> b) : b_(b) {}

   int value() {
     return b_->value();
   }
private:
   std::shared_ptr<IB> b_;
};

void MyModule(Container& c) {
 c.RegisterAs<IB, B>(CREATE(
   B()
 ));

 c.Register<A>(CREATE(
   A(INJECT(IB))
 ));
}

int main() {
 Container c;
 MyModule(c);
 std::cout << c.Get<A>()->value() << std::endl;
 return 0;
}

Вместо shared_ptr может вполне быть unique_ptr и B будет не синглтоном внутри контейнера, а будет создаваться отдельно для каждого класса-пользователя. Слово синглтон перестает быть пугающим, потому что это не глобальный синглтон, а синглтон в одном контейнере, плюс легко тестируется и нету проблем с неправильной инициализацией.

В тесте запросто выполняется MyModule, а потом регистрация IB меняется на MockB.

Примеры существующих фреймворков

https://github.com/google/fruit

https://github.com/ybainier/Hypodermic

Вопрос, чего не популярно? Врядли аргументы оправданы о том что это лишнее и все такое актуальны, пакетные менеджеры это решают. Зато тестирование на уровень легче, что уже с десяток лет используется в Java во все поля

★★★★★

В мире Java без этого тебя сначала засмеют, а потом когда поймут что ты не унимаешься - то изобьют.

Жестокий мир джунглей.

Вопрос, чего не популярно?

Потому-что KISS. Вот ты привел простыню кода, а что она делает? А нихрена, это аналог функции в одну строку, которую какого-то хрена извратили в непойми что. И это типичная болезнь джаверов - паттерны на паттепнах.

Зато тестирование на уровень легче, что уже с десяток лет используется в Java во все поля

Уродовать код в угоду тестам нужно далеко не всем и не всегда.

anonymous ()
Ответ на: комментарий от anonymous

Я так и думал что нихрена не поймут что же эта штука таки делает

а что она делает? А нихрена

в отличии от

это аналог функции в одну строку

vertexua ★★★★★ ()

Что я подразумеваю под IoC

ты описал реестр фабрик, и кодом:

 c.Register<A>(CREATE(
   A(INJECT(IB))
 ));

показал банальный servicelocator, в IoC объекты явно не создаются.

Deleted ()
Ответ на: комментарий от Deleted

Да, это один из способов регистрации. Например почему я именно так все написал, потому что видел уже такой фреймворк и там было реализовано что-то похожее на вот это

#define CREATE(x) [&](Container& cont) { return std::shared_ptr<...decltype...>(new x); }
#define INJECT(x) cont.Resolve<x>()

servicelocator

Не совсем, обычно этот локатор явно видно, в данном случае удобный DSL, в сами классы фреймворк не распространяется. Они самого локатора не видят никогда и получают готовые инстансы в конструктор

vertexua ★★★★★ ()
Ответ на: комментарий от vertexua

Я так и думал что нихрена не поймут что же эта штука таки делает

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

anonymous ()

И зачем это надо? Если

В тестах можно заменять целы модули указывая «запусти мне весь контейнер, но пожалуйста замени MyClass на MyMockClass»

то есть фреймворки для тестирования.

tailgunner ★★★★★ ()
Ответ на: комментарий от slackwarrior

Можно, но так обычно проще дла агломераций из 10 компонентов, один из которых доступ к файлам, а другой к БД. Их можно заменить

vertexua ★★★★★ ()

Группы регистраций классов можно обьединять в модули, которые просто устанавливаются подключением к основном контейнеру.

Когда же вы код-то пишете??

Не, серьезно.

arturpub ★★ ()
Ответ на: комментарий от arturpub

Когда менежмент получает живительных и раздает вниз по иерархии :)

slackwarrior ★★★★★ ()
Ответ на: комментарий от arturpub

Когда же вы код-то пишете??

За сегодня я написал строчку кода, потому что до меня индусы в spring написали их мульены и простое включение нескольких модулей привело к тому что нихера не работает 8)

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от arturpub

У меня в проекте это тоже не используется. Плюсовики в танке, о таком не слышали

vertexua ★★★★★ ()

Примеры существующих фреймворков

Обв требуют С++11, что ограничивает целевую аудиторию

Вопрос, чего не популярно?

Мало кто из плюсовиков захочет втыкать повсюду виртуальные функции ради каких-то там тестов. А передавать объекты через конструкторы можно и без регистрационной лапши.

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

Каким образом пакетные менеджеры решают проблему того, что это лишнее?

annulen ★★★★★ ()
Ответ на: комментарий от vertexua

каких-то там тестов
Мда

As of version 3.9.0, the SQLite library consists of approximately 112.8 KSLOC of C code. (KSLOC means thousands of «Source Lines Of Code» or, in other words, lines of code excluding blank lines and comments.) By comparison, the project has 811 times as much test code and test scripts - 91555.1 KSLOC.

Executive Summary

Three independently developed test harnesses 100% branch test coverage in an as-deployed configuration Millions and millions of test cases Out-of-memory tests I/O error tests Crash and power loss tests Fuzz tests Boundary value tests Disabled optimization tests Regression tests Malformed database tests Extensive use of assert() and run-time checks Valgrind analysis Undefined behavior checks Checklists

И ты не найдешь в коде SQLite ни одного подобного сраного паттерна. Ты вообще там не найдешь уродования кода для тестов. И сложность SQLite на порядки выше того, что делают обычные джавокодеры.

anonymous ()

А не проще просто для тестов подменять объектники? Можно чуть по другому скомпилировать либу и заменить my_cool_object.cpp на my_mock_cool_object.cpp. Да и в gcc есть атрибут weak. В сишке же сам бог велел просто подменять реализации функций на свои. Нахрена ради всего этого уродовать код?

Gorthauer ★★★★★ ()

Возможно, несколько полезных ссылок в тему:

http://ledentsov.de/2013/12/26/quest-for-a-cpp-dependency-injection-container...

http://meetingcpp.ru/?page_id=660

ЗЫ. Самому как-то никогда не нужно было. Вот что-то вроде системы плагинов, когда каждый плагин — это so-шка/dll-ка — вот это нужно бывало. А вот такой мелкий DI... Не вспоминается что-то.

eao197 ★★★★★ ()
Ответ на: комментарий от Deleted

Для тестов в том числе, потому что становится проще делать приложения более модульными, чем менее модульными

vertexua ★★★★★ ()
Ответ на: комментарий от Gorthauer

Это уж точно хороший способ острелить ноги. Код классов не уродуется. Классы остаются такими как были, просто теперь они склеиваются внутри модулей.

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

vertexua ★★★★★ ()
Ответ на: комментарий от vertexua

Сложнее H2, Cassandra, Spark, Hadoop, HDFS, Zookeeper?

А их написали типичные джавокодеры, которые могут писать только по паттернам? Или авторы H2, например, используют IoC? Если да - давай ссылку на код. Если нет...

anonymous ()
Ответ на: комментарий от anonymous

используют IoC

IoC в твоем понимании и описании ес-но, простой интерфейс и конструктор это и в плюсах частая практика.

anonymous ()
Ответ на: комментарий от anonymous

Я описал немного другой способ организации вызовов конструкторов и хранения инстансов. Например порядок регистрации не важен, легко слепо инсталлировать модули, главное чтобы сошелся паззл в итоге.

vertexua ★★★★★ ()
Ответ на: комментарий от vertexua

Например порядок регистрации не важен

Зато важен порядок деинициализации. Когда у тебя будут десятки сервисов, раскиданных по разным динамическим библиотекам и как-то хитро друг на друге завязанных, то вопрос просто завершения приложения станет вдруг весьма и весьма нетривиальным.

Gvidon ★★★★ ()
Ответ на: комментарий от vertexua

И чем тебя спасёт шаредптр, если у тебя сервисы А и Б имеют циклическую зависимость? А циклы при таком подходе образуются как два пальца, даже заметить не успеешь.

Gvidon ★★★★ ()
Ответ на: комментарий от Gvidon

Не имеют. Обьекты создаются через конструктор, потому перед тем как создать A нужно вычислить аргументы конструктора, например c.Resolve<B>(). А чтобы его создать, пусть через парочку промежуточных пунктов, будет необходим А. И вот тут взрыв, или переполнение стека при старте или явня проверка.

vertexua ★★★★★ ()
Ответ на: комментарий от vertexua

А кроме создания объектов у тебя в коде ничего происходить не будет? Циклы могут образовываться в произвольные моменты времени, не только при создании

Gvidon ★★★★ ()
Ответ на: комментарий от Gvidon

В идеале обьекты будут создаваться, а потом просто будут вызывать методы друг друга по уже установленым связям. Это работает неплохо на практике

vertexua ★★★★★ ()
Ответ на: комментарий от Gvidon

Одна из способностей подобных систем - какраз определять и как итог предотвращать появление циклических связей.

Deleted ()
Ответ на: комментарий от vertexua

Если у тебя при создании будут сразу разруливаться и подниматься все возможные зависимости, которые могут понадобиться объекту за время его существования, то это ппц.

Gvidon ★★★★ ()
Последнее исправление: Gvidon (всего исправлений: 1)
Ответ на: комментарий от Gvidon

Для случаев когда нужно например инстанциировать ряд обьектов на ходу, например во время обработки запроса извне, то можно произвести иньекцию самого контейнера, или враппера, который может просить контейнер создавать обьекты, но тогда внешний код должен следить что контейнер существует во все время существования запроса, плюс получать std::unique_ptr от интересующего обьекта и дать ему уничтожиться по завершению запроса.

vertexua ★★★★★ ()
Ответ на: комментарий от Deleted

Если вместо обьектов иногда инжектить фабрики, то без специальной проверки графа на циклы первое использование может быть позже и лишь потом упасть переполнением стека из-за цикла созданий обьекта.

Но опять же, можно в контейнере все зарегать, а потом выполнить c.Validate(), который рухнет при циклах

vertexua ★★★★★ ()
Ответ на: комментарий от Gvidon

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

Deleted ()
Ответ на: комментарий от vertexua

то без специальной проверки графа

Я вот офигел когда узнал что в спринге ее фактически нет.

Deleted ()
Ответ на: комментарий от Deleted

А как ты это иначе собираешься делать, если у тебя в произвольный момент времени кто угодно может дёрнуть фабрику и запросить у неё новый или где-то закешированный объект? Графы просто физически каждый раз разные.

Gvidon ★★★★ ()
Ответ на: комментарий от Deleted

Легко. Зависимости могут быть опциональными. Пользователь в настройках кнопочку нажал, какая-то подсистема отключилась. Ты её запросил, обломался и поехал дальше. Пользователь снова на кнопочку нажал, она поднялась, ты второй раз проходишь по тому же ровно пути, но у тебя вдруг новые зависимости.

Gvidon ★★★★ ()
Ответ на: комментарий от vertexua

Я описал немного другой способ организации вызовов конструкторов и хранения инстансов. Например порядок регистрации не важен, легко слепо инсталлировать модули, главное чтобы сошелся паззл в итоге.

Главный вопрос - зачем это все? Вот хочу я написать СУБД, например. Большая задача - тут и парсер, и работа с ФС, и сеть, и пользователей много, и многозадачность, и алгоритмов много самых разных. Где и зачем мне надо внедрять «твой» IoC?

anonymous ()
Ответ на: комментарий от Gvidon

Нет, при старте, после завершения регистрации. А потом считается что контейнер бесцикловый, но обьекты могут быть ленивыми

vertexua ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.