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 во все поля

★★★★★

Ответ на: комментарий от Norgat

Неа. Там вся соль в том, что конфигурацию MyModule в Java любят выносить в конфиги

Неа. Вся соль в том, что в яве все классы — чисто виртуальные. В яве каждый класс находится в своём файле, с набором соответствующих метаданных.

Очень легко забыть о «реальном мире», работая в виртуальной машине. Проблема в том, что C++ никогда из реального мира и не вылезал.

В реальном мире, для реализации IoC тебе нужно загрузить so'шку, получить адрес соответствующей процедуры, и её вызвать. Короче говоря, нужен стандартный ABI, пусть и специфичный для конкретной ОС/реализации C++.

Понятно дело, что во время компиляции неизвестны ни имя so'шки, ни где она лежит, ни имя процедуры. В этом и смысл IoC. Без динамической линковки она превращается в форменный онанизм.

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

Если скорость работы является чем-то стоящим конечно. Ведь как я сказал можно оптимизировать код с 2мс до 1мс при вызове 10 раз в минуту. За этим надо следить и оценивать наперед.

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

Такая была начальная теоретическая идея, но на практике все «пересобирают», по сути перекладывая в другое место файлы и запуская оттуда. Скорость с которой компилируется Java делают задачу экономии на времени компиляции неблагодарное затеей.

Если забыть что мы говорим о С++ на секунду, то в Java дело в другом, контейнер может иметь много начинки, например AOP. Когда говорят что все классы реализующие вот такой-то интерфейс или имеющие какую-то аннотацию например нужно завернуть в прокси и определенные методы завернуть в транзакцию или логгировать в какой-то специальный лог или БД. Когда в Java говорят что на интерфейс «A регистрируют класс B», то по настоящему имеют ввиду «Нужно выдать в виде A, какой-то класс, который контейнер решит использовать в данный момент, но вот есть класс B который в принципе по дефолту и содержит основную бизнес-логику»

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

Спасибо, я уже пишу один проект с разработчиками, которые любят писать все приложение в .h файлах.

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

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

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

А если обьектов 100, то они будут тоже позиционными в агрументах шаблона? Может у нас недопонимание но я не имел ввиду что «каждый объект типа контейнер работает только с одним классом».

Контейнер обычно один, в нем зарегистрировано все

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

А С++ обычно используют там, где важна скорость работы. Если писать код не думая о скорости и рассчитывать на то, что потом все боттлнеки найдутся, то потом наступает разочарование: после исправления алгоритмических тормозов остается равномерно тормозящяя масса.

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

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

vertexua ★★★★★
() автор топика

Как при таком раскладе контейнер передается в классы, которым нужны зависимости? Предположу, что придется таскать его через конструктор. В таком случае чем оно отличается от ServiceLocator?

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

А С++ обычно используют там, где важна скорость работы.

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

Я работал на работе по отдельности Java разработчиком, С# разработчиком, Scala разработчиком, Python разработчиком, C++ разработчиком, JavaScript/HTML5/CSS разработчиком, писал bash скрипты, OpenGL шейдеры и OpenCL-ядра, занимался анализом данных. Не могу себе представить что буду религиозно защищать динамически или статически типизированные языки, Microsoft или не Microsoft, функциональную или императивную парадигму, нативные приложения или VM, явное управление памятью, подсчет ссылок, сборщик мусора или модель вроде Rust. Могу легко прочитать лекцию о преимуществах чего угодно. Но попробуй анонимуса из этого треда писать на жабе какой-то сервис-координатор на paxos алгоритме, он тебе будет вопить о hft и о том что жаба-тормозит и все жаба-макаки вокруг.

У вас может больше опыта именно в С++, но не может ли быть так, что у вас просто такая доменная область. Много вещей на С++ будут написаны просто потому что например на С++ есть библиотеки определенного типа. Тормоза будут в том, что под грузом невозможности разобраться в громадной лапше инкто не может оценить эти ботлнеки. А с более простыми модулями было бы проще. Нельзя ли следить за тем, чтобы код просто не стал «равномерно тормозящей массой»

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

Как раз делают A<T> не потому что есть много реализаций T, а потому что эти реализации появляются в тестах. Хотя в реальной жизни мог бы быть IB, B, A, MockB не в заголовочных файлах.

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

Мне кажется что в среде С++ разработчиков костыль под названием «заголовочный файл» - это просто обьективная реальность с которой нужно мириться, потому «мириться» через время переходит в «обожать», чтобы не было так обидно.

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

Ваш код показывает работу только с одним классом. Как он будет работать с несколькими классами, неясно. Например, жёстко ли задан в рантайме список всех классов IB...IN с которыми могут работать ф-ции вида MyModule(). Что должно происходить, если не все классы инициализировать?

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

Никогда не связывал это с динамической линковкой.

И очень зря, потому что именно так обстоят дела с явой.

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

Контейнер в идеале никогда не передается в классы. Контенейр создает классы и заполняет аргументы их конструктором нужными инстансами других классов-зависимостей.

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

CREATE - макрос генерирующий на лямбду в котороую передается контейнер. INJECT - макрос, который достает из контейнера инстанс. Имя агрумента лямбды не просачивается в видимый код.

Иногда необходимо получить не один обьект раз и навсегда, а получить фабрику чтобы класс мог получать обьекты по требованию просто по имени типа, не заботясь о деталях создания обьекта, возлагая эту ответственность на контейнер. Тут очевидно будет скрытая ссылка на контейнер. Циклическую зависимость между обьектом и контейнером скорее всего придется развязывать через weak_ptr на контейнер и документировать что фабрика может и не сработать если контейнер будет уничтожен.

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

Пасоны, но вас послушать - так в крестах сейчас вообще ООП никому не нужно. Это правда?

Нет.

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

Я имел ввиду большие приложения.

Когда есть Config. DatabaseConnection использует Config.

UserRepository испольщует Config, Logging, DatabaseConnection.

PurchaseRepository использует Logging, DatabaseConnection.

Login использует Logging и UserRepository.

UserPurchases использует Logging, PurchaseRepository.

UI использует Login, UserPurchases.

А модули - только группы. Например все связанное с БД можно засунуть в модуль. Все связанное с безопасностью можно засунуть в модуль. Все связанное с UI можно засунуть в модуль. Это просто функция, которая заполнит контейнер нужными регистрациями.

Что должно происходить, если не все классы инициализировать?

Что значит инициализировались и по какой причине?

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

Хотя в реальной жизни мог бы быть IB, B, A, MockB не в

А и IB — это вообще классы-обслуга, какая разница, где они?

B

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

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

grep -R «using» ./*.h задача из задач, прямо

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

grep -R «using» ./*.h задача из задач, прямо

Проблема как раз в том что мы хотим использовать using в коде. А если весь код в заголовках, то мы приехали.

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

Потому что это пример с двумя классами. Если представить цепочку зависимостей A->B->C->D, то получается что у нас будет D, C<D>, B<C<D>>, A<B<C<D>>>. Нет спасибо

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

Мне кажется что в среде С++ разработчиков костыль под названием «заголовочный файл» - это просто обьективная реальность с которой нужно мириться, потому «мириться» через время переходит в «обожать», чтобы не было так обидно.

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

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

В среде С++ наследство С в виде костыля под названием «заголовочный файл» заслуженно считается костылем же.

Ты бы так за всю «среду» не говорил. Хватает ретроградов, которые не просто не понимают нафига это нужно, а ещё и считают модули чем-то вредным. На лоре такие тоже есть.

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

Ты бы так за всю «среду» не говорил. Хватает ретроградов, которые не просто не понимают нафига это нужно, а ещё и считают модули чем-то вредным. На лоре такие тоже есть.

Маргиналы есть везде, особенно на ЛОР, но кому до них есть дело? Модули прорабатывают для стандарта, в clang уже запилили свою начальную реализацию, аналогично в Visual C++. Все вменяемые люди это только приветствуют, т.к. препроцессор имеет свои очевидные недостатки, которые бьют прежде всего по новичкам.

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

Container<A, B, C> — шаблон с переменным числом аргументов. аргументы — классы типа В, а не другие контейнеры, поэтому никаких A<B<C<D> > > тут не будет

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

использование кода в виде каких-то готовых отдельных классов - это нереальное, высосанное из пальца обоснование для сильного усложнения кода

никому не нужны классы

Тебе басню рассказать про лису и виноград, или сам перечитаешь?

no-such-file ★★★★★
()

Кстати, иногда в такие контейнеры запихивают синхронный bus как способ decoupling циклических зависимостей вместо weak_ptr. Только тут пользователь тоже может получать уведомления о недоставке если он отправил сообщение во время освобождения контейнера или просто явно соглашаться игнорировать недоставку

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

лучше сказать, Module<A, B, C>(), а Container — std::list<BasicModule>. где Module<A, B, C> — шаблон с базовым классом BasicModule, а BasicModule — не шаблон.

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

std::list<BasicModule*> имелось в виду

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

Так бы сразу и сказали, что вам зависимости между модулями в рантайме надо отслеживать. Тогда, каждый из вышеприведённых модулей — в шаблон tree<> ещё запихнуть или graph<>, как общий случай. Сontainer тогда тоже не list<>, а tree<> или graph<>. А там уже средствами выбранного шаблона отслеживать, что хотите: хоть циклические связи, хоть что.

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

дык фабрика и шаблонный метод (например) — это тоже ioc. но он ещё не был на том тренинге жаба-кодеров где об это рассказывают.

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

миллиард итераций за 2мс. когда вы уже научитесь писать нормальные тесты производительности?

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

Это Линукс. Здесь бесконечный цикл выполняется за 10 с, а ты тут к жалкому миллиарду итераций привязался.

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

инкто не может оценить эти ботлнеки

и это хорошо. потому что тех кто их «оценивал» уже давно погнали ссаными тряпками

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

Я уже обьяснил что IoC - продолжение идеи Service Locator, так как он так же создает сами обьекты и управляет их жизненным циклом

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

Я просто обьяснил почему писать все приложение в заголовочных файлах недостойно благородных донов

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

не так. приходишь ты на лор и говоришь: меня вчера на тренинге научили скрещивать ежа с ужом factory и service locator; почему другие так не делают? если коротко: это неудобно; если подробнее: гугли srp.

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

нет, басни рассказывают теоретики, разнообразные пейсатели книжечек, сидящие на кафедрах и не написавшие ни одного реально полезного приложения. а у меня опыт разработки на С/C++ более 15 лет. одно дело играться с кодом ради развлечения, а другое дело - реальная работа на настоящем железе, когда нужна оптимизация и скорость.

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

говнокода в опенсорце много. но говнокодерство не относится к свойствам языка программирования. я лично такими продуктами просто не пользуюсь, по возможности. но и не лезу в разработку гуйни, потому что там процент говнокодеров многократно выше, нежели в бэкендах и драйверах. потому что шлёпать формочки в каком-нить креаторе всяко проще и туда активно лезут нубы и неосиляторы. и бороться с ними сложно, на это нужно много свободного времени, а у меня его нет. хотя, честно говоря, иногда руки чешутся форкнуть какой-нить проектик и допилить его вручную до нормального вменяемого состояния. например, в последнее время говнокод захватил мою любимую IDE-шечку, codeblocks. транк стал безобразно глючным, код конкретно поплыл. меня это раздражает, но ещё в прошлом мои обращения на форум разработчиков ни к чему не привели. варианта два: пользоваться старой версией или форкать и пилить свою. на целиком свою IDE времени точно не хватит. может, на пенсии этим займусь, если доживу. я не могу переделать весь мир. но говнокод не уважаю и не буду способствовать его распространению.

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

не написавшие ни одного реально полезного приложения

Ну конечно, что-то реальное и полезное в этой части вселенной делаешь только ты, остальные лохи.

реальная работа на настоящем железе, когда нужна оптимизация и скорость

Вот только в реальном мире всё немножко сложнее и обычно побеждают не соображения скорости, а соображения денег, так что понятие оптимального - вещь относительная и не всегда (а даже очень редко) оптимальное значит быстрое.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

vertexua ★★★★★
() автор топика

Штука весьма полезная, использовали в одном из проектов, где было несколько полиморфных объектов и несколько вариантов их обработки. Возможно, я путаю с Dependency Injection.

Больше нигде не пригодилось, бОльшая часть функциональности на плюсах пишется реюзабельнее и лаконичнее без IoC. Снизить связанность в плюсах не сможет, а кроме этого больше ни для чего не нужен, если я правильно понимаю.

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

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

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

Религиозность не очень компетентных программистов среди всех ЯП одинакова. Лучший пример религиозности многих программистов на С++ - этот тред. Диды так не делали, и мы не будем

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

Есть некоторая разница между «зачем нам непонятная мишура, когда уже всё работает» и «диды так не делали». Для того, чтобы переходить на новую модель, нужно сначала убедиться в её эффективности. Я лично до сих пор не вижу надобности переводить приложения целиком на IoC, я думаю, и другие программисты на C++ тоже не хотят впустую тратить время без ощутимого результата.

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