DI в C++ тонкости и
нюансы
а так же IoC контейнеры на примере
Hypodermic
Щербаков Антон
компания СИГНАТЕК
Обо мне:
- решения в области СОРМ и
телекоммуникаций
- 24x7 сервисы с минимум GUI
- непрерывное усиление и развитие
команды
О презентации:
- личный опыт использования DI и IoC
- примеры тестов на GMOCK и GTEST
- … и c++ :)
Специфика работы
- Много долгоживущих проектов
- Которые нужно дорабатывать
- Дорабатывать быстро и качественно
С чего все начиналось...
Выводы:
Код должен быть тестируем
Код должен быть тестируем просто
Тестовое окружение должно быть
максимально простым
Что же такое DI?
...Кто слышал?
Dependency Injection
это design pattern
● Dependency это объект, который
используется (Service)
● Injection - передача зависимости,
зависящему классу (Client-у). При этом
Dependency становится состоянием
класса.
Пример с кофеваркой
/* Без внедрения зависимостей */
class CoffeMaker {
public:
CoffeMaker() {
m_heater = new Heater();
m_pump = new Pump();
}
Coffe MakeCoffe() { /* ... */ }
private:
Heater* m_heater;
Pump* m_pump;
};
int main() {
CoffeMaker* coffeMaker = new CoffeMaker();
coffeMaker->MakeCoffe();
}
/* Ручное внедрение зависимостей */
class CoffeMaker {
public:
CoffeMaker( Heater* heater, Pump* pump ) {
m_heater = heater;
m_pump = pump;
}
Coffe MakeCoffe() { /* ... */ }
private:
Heater* m_heater;
Pump* m_pump;
};
int main() {
Heater* heater = new Heater();
Pump* pump = new Pump();
CoffeMaker* coffeMaker = new CoffeMaker( heater,
pump );
coffeMaker->MakeCoffe();
}
class Heater : public IHeater {
public:
virtual void Heat() = 0;
};
class Pump : public IPump {
public:
virtual void Drip() = 0;
};
class CoffeMaker {
public:
CoffeMaker( IHeater* heater, IPump* pump ) {
m_heater = heater;
m_pump = pump;
}
Coffe MakeCoffe() { /* ... */ }
private:
IHeater* m_heater;
IPump* m_pump;
};
int main() {
// Пользователь класса определяет что именно
передавать
IHeater* heater = new Heater();
IPump* pump = new Pump();
CoffeMaker* coffeMaker = new CoffeMaker( heater,
pump );
coffeMaker->MakeCoffe();
}
Что получаем?
Кого инстанциировать решает пользователь
(MOCK, STUB)
Получаем слабою связность
Класс стал проще тестироваться
class Mock_IHeater : public IHeater {
public:
MOCK_METHOD0( Heat, void() );
};
class Mock_IPump : public IPump {
public:
MOCK_METHOD0( Drip, void() );
};
TEST_F( CoffeMakerTest, Should_heat_water ) {
Mock_IHeater heater;
Mock_IPump pump;
CoffeMaker coffeMaker( &heater, &pump );
EXPECT_CALL( pump, Drip() )
.Times( 1 );
EXPECT_CALL( heater, Heat() )
.Times( 1 );
coffeMaker.MakeCoffe();
}
Минусы?
Избыточный дизайн (чем как правило нужно)
“Архитектурный” шум в виде интерфейсов
НО тестируемость важнее!
Зависимость через указатель
Как контролировать потерю объекта?
А при многопоточности?
Кто отвечает за время жизни зависимости?
Чистые указатели - это уже дурной тон
Какие альтернативы?
std/boost:
shared_ptr
unique_ptr
weak_ptr
unique_ptr
Только класс клиент владеет и отвечает за
время жизни зависимоости.
Но тестировать не получится поэтому
unique_ptr
shared_ptr
Класс клиент так же отвечает за время
жизни
Или ответственность передана полностью
Потокобезопасен
НО есть проблема с циклическими связями
weak_ptr
За время жизни класс не отвечает и готов к
тому, что ее могут отнять
В качестве способа передачи
зависимости остались:
shared_ptr
weak_ptr
Внедряем зависимости:
Конструктор
как правило shared_ptr
Set-тер
как правило weak_ptr
class CoffeMaker {
public:
CoffeMaker( std::shared_ptr< IHeater > heater, std::shared_ptr< IPump > pump ) {
m_heater = heater;
m_pump = pump;
}
void SetBell( std::weak_ptr< IBell > bell ) {
m_bell = bell;
}
Coffe MakeCoffe() {
/* ... */
}
private:
std::shared_ptr< IHeater > m_heater; // Класс не может делать кофе без насоса и нагревателя
std::shared_ptr< IPump > m_pump;
std::weak_ptr< IBell > m_bell; // Класс может делать кофе и без колокольчика
};
Внедряем зависимости с weak_ptr
При DI лучше обходиться без
weak_ptr
Циклические связи - плохой “запах” в
архитектуре и дизайне
class CoffeMaker {
public:
CoffeMaker( std::shared_ptr< IHeater > heater, std::shared_ptr< IPump > pump ) {
m_heater = heater;
m_pump = pump;
}
/* ... */
private:
std::shared_ptr< IHeater > m_heater;
std::shared_ptr< IPump > m_pump;
};
TEST_F( CoffeMakerTest, Should_heat_water ) {
std::shared_ptr< IHeater > heater = std::make_shared< Mock_IHeater >();
std::shared_ptr< IPump > pump = std::make_shared< Mock_IPump >();
CoffeMaker coffeMaker( heater, pump );
EXPECT_CALL( *pump, Drip() )
.Times( 1 );
EXPECT_CALL( *heater, Heat() )
.Times( 1 );
coffeMaker.MakeCoffe();
}
Все хорошо… НО
Кто все это будет конструировать?
А внедрять зависимости?
И как это проверять?
Пример ручного связывания
{
auto transportLayer = std::make_shared< TcpTransportSystem >();
auto sormController = std::make_shared< SormControllerComponent >( sormType, acceptTimeOut );
auto sormMsgProcessor = std::make_shared< SormMsgProcessor >();
auto sormTransport = std::make_shared< TcpTransport >( transportLayer sormIp, sormPort, sormMessageProcessor );
auto sormComponent = std::make_shared< SormComponent >( sormType, sormController, sormTransport );
auto puTranspoort = std::make_shared< TcpTransport >( puIp, puPort, sormMessageProcessor );
auto puComponent = std::make_shared< PuModule >( puTransport );
auto operationScheduler = std::make_shared< RealTimeOperationScheduler >();
auto ioController = std::make_shared< IoController >( operationScheduler );
auto app = std::make_shared< DelveryService >( sormComponent, puComponent, transportLayer, ioController );
app.Start();
app.Release();
}
Composition root и Register Resolve
Release
{
// Register
auto transportLayer = std::make_shared< TcpTransportSystem >();
auto sormController = std::make_shared< SormControllerComponent >( sormType, acceptTimeOut );
auto sormMsgProcessor = std::make_shared< SormMsgProcessor >();
auto sormTransport = std::make_shared< TcpTransport >( transportLayer sormIp, sormPort, sormMessageProcessor );
auto sormComponent = std::make_shared< SormComponent >( sormType, sormController, sormTransport );
auto puTranspoort = std::make_shared< TcpTransport >( puIp, puPort, sormMessageProcessor );
auto puComponent = std::make_shared< PuModule >( puTransport );
auto operationScheduler = std::make_shared< RealTimeOperationScheduler >();
auto ioController = std::make_shared< IoController >( operationScheduler );
// Resolve
auto app = std::make_shared< DelveryService >( sormComponent, puComponent, transportLayer, ioController );
app.Start();
// Release
app.Release();
}
IoC контейнеры
Регистрация реализации для интерфейса
Разрешают необходимые зависимости
Выполняют авто связывание
Hypodermic
int main()
{
// Register
ContainerBuilder builder;
builder.registerType< Heater >().as< IHeater >();
builder.registerType< Pump >().as< IPump >();
builder.registerType< CoffeMaker >(
CREATE( std::make_shared< CoffeMaker >(
INJECT( IHeater ),
INJECT( IPump ) ) ) );
auto container = builder.build();
// Resolve
auto coffeMaker = container->resolve< CoffeMaker >();
coffeMaker->MakeCoffe();
}
Регистрация типа
{
ContainerBuilder builder;
// Регистрация непосредственно имплементации
builder.registerType< Pump >();
auto container = builder.build();
auto pump = container->resolve< Pump >();
ASSERT_TRUE( pump != nullptr );
}
Регистрация типа
{
ContainerBuilder builder;
// Регистрация по интерфейсу
builder.registerType< Pump >().as< IPump >();
auto container = builder.build();
auto pump= container->resolve< IPump >();
ASSERT_TRUE( pump != nullptr );
}
Регистрация типа
{
ContainerBuilder builder;
// Регистрация по интерфейсу и непосредственно имплементации
builder.registerType< Pump >().as< IPump >().asSelf();
auto container = builder.build();
auto concreetePump = container->resolve< Pump >();
auto abstractPump = container->resolve< IPump >();
ASSERT_TRUE( concreetePump != nullptr );
ASSERT_TRUE( abstractPump != nullptr );
}
Регистрация типа
{
ContainerBuilder builder;
// Регистрация экземпляра
auto pump = std::make_shared< Pump >();
builder.registerInstance( pump );
auto container = builder.build();
auto samePump = container->resolve< IPump >();
ASSERT_TRUE( samePump == pump );
}
Регистрация типа
{
ContainerBuilder builder;
// SingleInstance
builder.registerType< Pump >().as< IPump >().singleInstance();
auto container = builder.build();
auto pump = container->resolve< IPump >();
auto samePump = container->resolve< IPump >();
ASSERT_TRUE( samePump == pump );
}
Регистрация типа
{
ContainerBuilder builder;
// Регистрация одной имплементации по нескольким интерфейсам
builder.registerType< PumpAndHeater >().as< IPump >().as< IHeater >();
auto container = builder.build();
auto pump = container->resolve< IPump >();
auto heater = container->resolve< IHeater>();
// Опустим cast-ы для наглядности
ASSERT_TRUE( heater == pump );
}
Именованная регистрация типа
{
ContainerBuilder builder;
auto pump1 = std::make_shared< Pump >();
auto pump2 = std::make_shared< Pump >();
builder.registerInstance( pump1 ).named< IPump >( "pump1" );
builder.registerInstance( pump2 ).named< IPump >( "pump2" );
auto container = builder.build();
ASSERT_TRUE( container->resolveNamed< IPump >( "pump1" ) == pump1 );
ASSERT_TRUE( container->resolveNamed< IPump >( "pump2" ) == pump2 );
}
Инжекция зависимостей
{
ContainerBuilder builder;
builder.registerType< Pump >().as< IPump >();
builder.registerType< Heater >().as< IHeater >();
builder.registerType< CoffeMaker >(
CREATE( std::make_shared< CoffeMaker >(
INJECT( IHeater ),
INJECT( IPump ) ) ) ).as< CoffeMaker >();
auto container = builder.build();
auto coffeMaker = container->resolve< CoffeMaker >();
ASSERT_TRUE( coffeMaker != nullptr );
}
Инжекция зависимостей
{
ContainerBuilder builder;
builder.registerType< Pump >().named< IPump >( "pump" );
builder.registerType< Heater >().named< IHeater >( "heater" );
builder.registerType< CoffeMaker >(
CREATE( std::make_shared< CoffeMaker >(
INJECT_NAMED( IHeater, "heater" ),
INJECT_NAMED( IPump, "pump" ) ) ) ).as< CoffeMaker >();
auto container = builder.build();
auto coffeMaker = container->resolve< CoffeMaker >();
ASSERT_TRUE( coffeMaker != nullptr );
}
Регистрация через автосвязывание
struct ServiceA : IServiceA {
typedef AutowiredConstructor< ServiceA() > AutowiredSignature;
...
};
struct ServiceB : IServiceB {
typedef AutowiredConstructor< ServiceB(IServiceA*) > AutowiredSignature;
ServiceB( std::shared_ptr< IServiceA > serviceA )
: serviceA_( serviceA )
{
}
private:
std::shared_ptr< IServiceA > serviceA_;
};
Resolve через автосвязывание
{
ContainerBuilder builder;
builder.autowireType< ServiceA >().as< IServiceA >();
builder.autowireType< ServiceB >().singleInstance();
auto container = c.build();
auto serviceB = container->resolve< ServiceB >();
ASSERT_TRUE( serviceB != nullptr );
}
Как решать проблемы связывания?
Кидать исключение если зависимость не
задана
Кидать исключение если зависимость nullptr
Статика и динамика в IoC
IoC контейнеры позволяют создать каркас
приложения
IoC контейнеры не нужно передавать в
классы
Итоги:
DI в C++ вполне работает
Есть своя специфика в C++ и всегда нужно
держать ее в голове
Подход нужно рассматривать комплексно
Другие IoC контейнеры:
boost::di - header only!
walaroo
Как можно облегчить себе жизнь?
Для GMOCK есть python скрипт для
генерации кода моков
TypemockIsolator++
TEST( CoffeMakerTest, Should_heat_and_drip_water )
{
auto mockPump = FAKE< IPump >();
auto mockHeater = FAKE< IHeater >();
// Код теста...
}
TEST_F( IsolatorPPTests, IsExpired_YearIs2018_ReturnTrue )
{
Product product;
// Подготавливаем время для теста
SYSTEMTIME fakeTime;
fakeTime.wYear = 2018;
// Подделываем вызов системной функции
FAKE_GLOBAL( GetSystemTime );
WHEN_CALLED( GetSystemTime( RET( &fakeTime ) ) ).Ignore();
ASSERT_TRUE(product.IsExpired());
}
TypemockIsolator++
Для legacy кода.
Только Windows :(
А теперь вопросы
?

DI в C++ тонкости и нюансы

  • 1.
    DI в C++тонкости и нюансы а так же IoC контейнеры на примере Hypodermic Щербаков Антон компания СИГНАТЕК
  • 2.
    Обо мне: - решенияв области СОРМ и телекоммуникаций - 24x7 сервисы с минимум GUI - непрерывное усиление и развитие команды
  • 3.
    О презентации: - личныйопыт использования DI и IoC - примеры тестов на GMOCK и GTEST - … и c++ :)
  • 4.
    Специфика работы - Многодолгоживущих проектов - Которые нужно дорабатывать - Дорабатывать быстро и качественно
  • 5.
    С чего всеначиналось...
  • 6.
    Выводы: Код должен бытьтестируем Код должен быть тестируем просто Тестовое окружение должно быть максимально простым
  • 7.
    Что же такоеDI? ...Кто слышал?
  • 8.
    Dependency Injection это designpattern ● Dependency это объект, который используется (Service) ● Injection - передача зависимости, зависящему классу (Client-у). При этом Dependency становится состоянием класса.
  • 9.
    Пример с кофеваркой /*Без внедрения зависимостей */ class CoffeMaker { public: CoffeMaker() { m_heater = new Heater(); m_pump = new Pump(); } Coffe MakeCoffe() { /* ... */ } private: Heater* m_heater; Pump* m_pump; }; int main() { CoffeMaker* coffeMaker = new CoffeMaker(); coffeMaker->MakeCoffe(); } /* Ручное внедрение зависимостей */ class CoffeMaker { public: CoffeMaker( Heater* heater, Pump* pump ) { m_heater = heater; m_pump = pump; } Coffe MakeCoffe() { /* ... */ } private: Heater* m_heater; Pump* m_pump; }; int main() { Heater* heater = new Heater(); Pump* pump = new Pump(); CoffeMaker* coffeMaker = new CoffeMaker( heater, pump ); coffeMaker->MakeCoffe(); }
  • 10.
    class Heater :public IHeater { public: virtual void Heat() = 0; }; class Pump : public IPump { public: virtual void Drip() = 0; }; class CoffeMaker { public: CoffeMaker( IHeater* heater, IPump* pump ) { m_heater = heater; m_pump = pump; } Coffe MakeCoffe() { /* ... */ } private: IHeater* m_heater; IPump* m_pump; }; int main() { // Пользователь класса определяет что именно передавать IHeater* heater = new Heater(); IPump* pump = new Pump(); CoffeMaker* coffeMaker = new CoffeMaker( heater, pump ); coffeMaker->MakeCoffe(); }
  • 11.
    Что получаем? Кого инстанциироватьрешает пользователь (MOCK, STUB) Получаем слабою связность Класс стал проще тестироваться
  • 12.
    class Mock_IHeater :public IHeater { public: MOCK_METHOD0( Heat, void() ); }; class Mock_IPump : public IPump { public: MOCK_METHOD0( Drip, void() ); }; TEST_F( CoffeMakerTest, Should_heat_water ) { Mock_IHeater heater; Mock_IPump pump; CoffeMaker coffeMaker( &heater, &pump ); EXPECT_CALL( pump, Drip() ) .Times( 1 ); EXPECT_CALL( heater, Heat() ) .Times( 1 ); coffeMaker.MakeCoffe(); }
  • 13.
    Минусы? Избыточный дизайн (чемкак правило нужно) “Архитектурный” шум в виде интерфейсов НО тестируемость важнее!
  • 14.
    Зависимость через указатель Какконтролировать потерю объекта? А при многопоточности? Кто отвечает за время жизни зависимости? Чистые указатели - это уже дурной тон
  • 15.
  • 16.
    unique_ptr Только класс клиентвладеет и отвечает за время жизни зависимоости. Но тестировать не получится поэтому unique_ptr
  • 17.
    shared_ptr Класс клиент также отвечает за время жизни Или ответственность передана полностью Потокобезопасен НО есть проблема с циклическими связями
  • 18.
    weak_ptr За время жизникласс не отвечает и готов к тому, что ее могут отнять
  • 19.
    В качестве способапередачи зависимости остались: shared_ptr weak_ptr
  • 20.
  • 21.
    class CoffeMaker { public: CoffeMaker(std::shared_ptr< IHeater > heater, std::shared_ptr< IPump > pump ) { m_heater = heater; m_pump = pump; } void SetBell( std::weak_ptr< IBell > bell ) { m_bell = bell; } Coffe MakeCoffe() { /* ... */ } private: std::shared_ptr< IHeater > m_heater; // Класс не может делать кофе без насоса и нагревателя std::shared_ptr< IPump > m_pump; std::weak_ptr< IBell > m_bell; // Класс может делать кофе и без колокольчика }; Внедряем зависимости с weak_ptr
  • 22.
    При DI лучшеобходиться без weak_ptr Циклические связи - плохой “запах” в архитектуре и дизайне
  • 23.
    class CoffeMaker { public: CoffeMaker(std::shared_ptr< IHeater > heater, std::shared_ptr< IPump > pump ) { m_heater = heater; m_pump = pump; } /* ... */ private: std::shared_ptr< IHeater > m_heater; std::shared_ptr< IPump > m_pump; }; TEST_F( CoffeMakerTest, Should_heat_water ) { std::shared_ptr< IHeater > heater = std::make_shared< Mock_IHeater >(); std::shared_ptr< IPump > pump = std::make_shared< Mock_IPump >(); CoffeMaker coffeMaker( heater, pump ); EXPECT_CALL( *pump, Drip() ) .Times( 1 ); EXPECT_CALL( *heater, Heat() ) .Times( 1 ); coffeMaker.MakeCoffe(); }
  • 24.
    Все хорошо… НО Ктовсе это будет конструировать? А внедрять зависимости? И как это проверять?
  • 25.
    Пример ручного связывания { autotransportLayer = std::make_shared< TcpTransportSystem >(); auto sormController = std::make_shared< SormControllerComponent >( sormType, acceptTimeOut ); auto sormMsgProcessor = std::make_shared< SormMsgProcessor >(); auto sormTransport = std::make_shared< TcpTransport >( transportLayer sormIp, sormPort, sormMessageProcessor ); auto sormComponent = std::make_shared< SormComponent >( sormType, sormController, sormTransport ); auto puTranspoort = std::make_shared< TcpTransport >( puIp, puPort, sormMessageProcessor ); auto puComponent = std::make_shared< PuModule >( puTransport ); auto operationScheduler = std::make_shared< RealTimeOperationScheduler >(); auto ioController = std::make_shared< IoController >( operationScheduler ); auto app = std::make_shared< DelveryService >( sormComponent, puComponent, transportLayer, ioController ); app.Start(); app.Release(); }
  • 26.
    Composition root иRegister Resolve Release { // Register auto transportLayer = std::make_shared< TcpTransportSystem >(); auto sormController = std::make_shared< SormControllerComponent >( sormType, acceptTimeOut ); auto sormMsgProcessor = std::make_shared< SormMsgProcessor >(); auto sormTransport = std::make_shared< TcpTransport >( transportLayer sormIp, sormPort, sormMessageProcessor ); auto sormComponent = std::make_shared< SormComponent >( sormType, sormController, sormTransport ); auto puTranspoort = std::make_shared< TcpTransport >( puIp, puPort, sormMessageProcessor ); auto puComponent = std::make_shared< PuModule >( puTransport ); auto operationScheduler = std::make_shared< RealTimeOperationScheduler >(); auto ioController = std::make_shared< IoController >( operationScheduler ); // Resolve auto app = std::make_shared< DelveryService >( sormComponent, puComponent, transportLayer, ioController ); app.Start(); // Release app.Release(); }
  • 27.
    IoC контейнеры Регистрация реализациидля интерфейса Разрешают необходимые зависимости Выполняют авто связывание
  • 28.
    Hypodermic int main() { // Register ContainerBuilderbuilder; builder.registerType< Heater >().as< IHeater >(); builder.registerType< Pump >().as< IPump >(); builder.registerType< CoffeMaker >( CREATE( std::make_shared< CoffeMaker >( INJECT( IHeater ), INJECT( IPump ) ) ) ); auto container = builder.build(); // Resolve auto coffeMaker = container->resolve< CoffeMaker >(); coffeMaker->MakeCoffe(); }
  • 29.
    Регистрация типа { ContainerBuilder builder; //Регистрация непосредственно имплементации builder.registerType< Pump >(); auto container = builder.build(); auto pump = container->resolve< Pump >(); ASSERT_TRUE( pump != nullptr ); }
  • 30.
    Регистрация типа { ContainerBuilder builder; //Регистрация по интерфейсу builder.registerType< Pump >().as< IPump >(); auto container = builder.build(); auto pump= container->resolve< IPump >(); ASSERT_TRUE( pump != nullptr ); }
  • 31.
    Регистрация типа { ContainerBuilder builder; //Регистрация по интерфейсу и непосредственно имплементации builder.registerType< Pump >().as< IPump >().asSelf(); auto container = builder.build(); auto concreetePump = container->resolve< Pump >(); auto abstractPump = container->resolve< IPump >(); ASSERT_TRUE( concreetePump != nullptr ); ASSERT_TRUE( abstractPump != nullptr ); }
  • 32.
    Регистрация типа { ContainerBuilder builder; //Регистрация экземпляра auto pump = std::make_shared< Pump >(); builder.registerInstance( pump ); auto container = builder.build(); auto samePump = container->resolve< IPump >(); ASSERT_TRUE( samePump == pump ); }
  • 33.
    Регистрация типа { ContainerBuilder builder; //SingleInstance builder.registerType< Pump >().as< IPump >().singleInstance(); auto container = builder.build(); auto pump = container->resolve< IPump >(); auto samePump = container->resolve< IPump >(); ASSERT_TRUE( samePump == pump ); }
  • 34.
    Регистрация типа { ContainerBuilder builder; //Регистрация одной имплементации по нескольким интерфейсам builder.registerType< PumpAndHeater >().as< IPump >().as< IHeater >(); auto container = builder.build(); auto pump = container->resolve< IPump >(); auto heater = container->resolve< IHeater>(); // Опустим cast-ы для наглядности ASSERT_TRUE( heater == pump ); }
  • 35.
    Именованная регистрация типа { ContainerBuilderbuilder; auto pump1 = std::make_shared< Pump >(); auto pump2 = std::make_shared< Pump >(); builder.registerInstance( pump1 ).named< IPump >( "pump1" ); builder.registerInstance( pump2 ).named< IPump >( "pump2" ); auto container = builder.build(); ASSERT_TRUE( container->resolveNamed< IPump >( "pump1" ) == pump1 ); ASSERT_TRUE( container->resolveNamed< IPump >( "pump2" ) == pump2 ); }
  • 36.
    Инжекция зависимостей { ContainerBuilder builder; builder.registerType<Pump >().as< IPump >(); builder.registerType< Heater >().as< IHeater >(); builder.registerType< CoffeMaker >( CREATE( std::make_shared< CoffeMaker >( INJECT( IHeater ), INJECT( IPump ) ) ) ).as< CoffeMaker >(); auto container = builder.build(); auto coffeMaker = container->resolve< CoffeMaker >(); ASSERT_TRUE( coffeMaker != nullptr ); }
  • 37.
    Инжекция зависимостей { ContainerBuilder builder; builder.registerType<Pump >().named< IPump >( "pump" ); builder.registerType< Heater >().named< IHeater >( "heater" ); builder.registerType< CoffeMaker >( CREATE( std::make_shared< CoffeMaker >( INJECT_NAMED( IHeater, "heater" ), INJECT_NAMED( IPump, "pump" ) ) ) ).as< CoffeMaker >(); auto container = builder.build(); auto coffeMaker = container->resolve< CoffeMaker >(); ASSERT_TRUE( coffeMaker != nullptr ); }
  • 38.
    Регистрация через автосвязывание structServiceA : IServiceA { typedef AutowiredConstructor< ServiceA() > AutowiredSignature; ... }; struct ServiceB : IServiceB { typedef AutowiredConstructor< ServiceB(IServiceA*) > AutowiredSignature; ServiceB( std::shared_ptr< IServiceA > serviceA ) : serviceA_( serviceA ) { } private: std::shared_ptr< IServiceA > serviceA_; };
  • 39.
    Resolve через автосвязывание { ContainerBuilderbuilder; builder.autowireType< ServiceA >().as< IServiceA >(); builder.autowireType< ServiceB >().singleInstance(); auto container = c.build(); auto serviceB = container->resolve< ServiceB >(); ASSERT_TRUE( serviceB != nullptr ); }
  • 40.
    Как решать проблемысвязывания? Кидать исключение если зависимость не задана Кидать исключение если зависимость nullptr
  • 41.
    Статика и динамикав IoC IoC контейнеры позволяют создать каркас приложения IoC контейнеры не нужно передавать в классы
  • 42.
    Итоги: DI в C++вполне работает Есть своя специфика в C++ и всегда нужно держать ее в голове Подход нужно рассматривать комплексно
  • 43.
  • 44.
    Как можно облегчитьсебе жизнь? Для GMOCK есть python скрипт для генерации кода моков
  • 45.
    TypemockIsolator++ TEST( CoffeMakerTest, Should_heat_and_drip_water) { auto mockPump = FAKE< IPump >(); auto mockHeater = FAKE< IHeater >(); // Код теста... } TEST_F( IsolatorPPTests, IsExpired_YearIs2018_ReturnTrue ) { Product product; // Подготавливаем время для теста SYSTEMTIME fakeTime; fakeTime.wYear = 2018; // Подделываем вызов системной функции FAKE_GLOBAL( GetSystemTime ); WHEN_CALLED( GetSystemTime( RET( &fakeTime ) ) ).Ignore(); ASSERT_TRUE(product.IsExpired()); }
  • 46.
  • 47.