Top Banner
DI в C++ тонкости и нюансы а так же IoC контейнеры на примере Hypodermic Щербаков Антон компания СИГНАТЕК
47

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

Apr 15, 2017

Download

Software

Platonov Sergey
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: DI в C++ тонкости и нюансы

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

а так же IoC контейнеры на примере Hypodermic

Щербаков Антонкомпания СИГНАТЕК

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

Обо мне:

- решения в области СОРМ и телекоммуникаций

- 24x7 сервисы с минимум GUI- непрерывное усиление и развитие

команды

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

О презентации:

- личный опыт использования DI и IoC- примеры тестов на GMOCK и GTEST- … и c++ :)

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

Специфика работы

- Много долгоживущих проектов- Которые нужно дорабатывать- Дорабатывать быстро и качественно

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

С чего все начиналось...

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

Выводы:

Код должен быть тестируемКод должен быть тестируем простоТестовое окружение должно быть максимально простым

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

Что же такое DI?

...Кто слышал?

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

Dependency Injection

это design pattern● Dependency это объект, который

используется (Service)● Injection - передача зависимости,

зависящему классу (Client-у). При этом Dependency становится состоянием класса.

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

Пример с кофеваркой/* Без внедрения зависимостей */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();

}

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

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();

}

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

Что получаем?

Кого инстанциировать решает пользователь(MOCK, STUB)

Получаем слабою связностьКласс стал проще тестироваться

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

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();

}

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

Минусы?

Избыточный дизайн (чем как правило нужно)“Архитектурный” шум в виде интерфейсов

НО тестируемость важнее!

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

Зависимость через указатель

Как контролировать потерю объекта?А при многопоточности?Кто отвечает за время жизни зависимости?Чистые указатели - это уже дурной тон

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

Какие альтернативы?

std/boost:shared_ptrunique_ptrweak_ptr

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

unique_ptr

Только класс клиент владеет и отвечает за время жизни зависимоости.Но тестировать не получится поэтомуunique_ptr

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

shared_ptr

Класс клиент так же отвечает за время жизниИли ответственность передана полностьюПотокобезопасен

НО есть проблема с циклическими связями

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

weak_ptr

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

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

В качестве способа передачи зависимости остались:shared_ptrweak_ptr

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

Внедряем зависимости:

Конструктор как правило shared_ptr

Set-теркак правило weak_ptr

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

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

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

При DI лучше обходиться без weak_ptr

Циклические связи - плохой “запах” в архитектуре и дизайне

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

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();}

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

Все хорошо… НО

Кто все это будет конструировать?А внедрять зависимости?И как это проверять?

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

Пример ручного связывания {

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();}

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

Composition root и Register Resolve Release

{// Registerauto 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 );

// Resolveauto app = std::make_shared< DelveryService >( sormComponent, puComponent, transportLayer, ioController );app.Start();

// Releaseapp.Release();

}

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

IoC контейнеры

Регистрация реализации для интерфейсаРазрешают необходимые зависимостиВыполняют авто связывание

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

Hypodermicint main(){

// RegisterContainerBuilder 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();

// Resolveauto coffeMaker = container->resolve< CoffeMaker >();coffeMaker->MakeCoffe();

}

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

Регистрация типа{

ContainerBuilder builder;

// Регистрация непосредственно имплементацииbuilder.registerType< Pump >();

auto container = builder.build();auto pump = container->resolve< Pump >();

ASSERT_TRUE( pump != nullptr );}

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

Регистрация типа{

ContainerBuilder builder;

// Регистрация по интерфейсуbuilder.registerType< Pump >().as< IPump >();

auto container = builder.build();auto pump= container->resolve< IPump >();

ASSERT_TRUE( pump != nullptr );}

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

Регистрация типа{

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 );}

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

Регистрация типа{

ContainerBuilder builder;

// Регистрация экземпляраauto pump = std::make_shared< Pump >();builder.registerInstance( pump );

auto container = builder.build();auto samePump = container->resolve< IPump >();

ASSERT_TRUE( samePump == pump );}

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

Регистрация типа{

ContainerBuilder builder;

// SingleInstancebuilder.registerType< Pump >().as< IPump >().singleInstance();

auto container = builder.build();auto pump = container->resolve< IPump >();auto samePump = container->resolve< IPump >();

ASSERT_TRUE( samePump == pump );}

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

Регистрация типа{

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 );

}

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

Именованная регистрация типа{

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 );

}

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

Инжекция зависимостей{

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 );}

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

Инжекция зависимостей{

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 );}

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

Регистрация через автосвязывание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_;};

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

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 );}

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

Как решать проблемы связывания?

Кидать исключение если зависимость не заданаКидать исключение если зависимость nullptr

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

Статика и динамика в IoC

IoC контейнеры позволяют создать каркас приложенияIoC контейнеры не нужно передавать в классы

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

Итоги:

DI в C++ вполне работаетЕсть своя специфика в C++ и всегда нужно держать ее в головеПодход нужно рассматривать комплексно

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

Другие IoC контейнеры:

boost::di - header only!walaroo

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

Как можно облегчить себе жизнь?

Для GMOCK есть python скрипт для генерации кода моков

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

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());}

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

TypemockIsolator++

Для legacy кода. Только Windows :(

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

А теперь вопросы

?