Последнее обновление:2026-10-12
Внедрение зависимостей в C++¶
Недавно один из участников нашего сообщества попросил написать про внедрение зависимостей, так и появилась эта статья с разбором на практических примерах. В ней мы рассмотрим ряд принципов и реализаций; полная «ленивая» версия кода выложена в сообществе. Начнём с основного.
План:
- Прямая зависимость
- Внедрение зависимости через интерфейс
- Внедрение зависимости через шаблоны
Внедрение зависимостей — часто используемый шаблон проектирования, в котором обычно применяются интерфейсы или шаблоны, чтобы повысить тестируемость и сопровождаемость кода. Рассмотрим пример.
Предположим, есть класс пользовательского сервиса UserService, которому необходимо взаимодействовать с удалённым сервером через HTTP-клиент. В этом случае мы могли бы написать следующие три реализации.
1. Прямая зависимость¶
UserService внутри напрямую зависит от HttpClient, например:
class HttpClient {
public:
std::string Get(const std::string& url);
void Post(const std::string& url, const std::string& data);
};
class UserService {
private:
HttpClient m_httpClient;
};
Внутри UserService обычно есть методы, работающие с m_httpClient. Такой способ прост и груб, однако UserService жёстко связан с конкретным HttpClient, из-за чего трудно подменить зависимый объект и трудно писать модульные тесты.
Отсюда вытекают следующие два способа.
2. Внедрение зависимости через интерфейс¶
Наша цель — сделать тестирование удобным, чтобы не приходилось создавать конкретный класс: пользователь может сделать mock-класс и тестировать с его помощью. Тогда нам на ум приходит интерфейс: выносим HttpClient в абстракцию и получаем:
struct IHttpClient {
virtual std::string Get(const std::string& url) = 0;
virtual void Post(const std::string& url, const std::string& data) = 0;
virtual ~IHttpClient() = default;
};
class UserService {
private:
std::unique_ptr<IHttpClient> m_httpClient;
};
Теперь при тестировании можно сделать mock-клиента и тестировать, например:
class MockHttpClient : public IHttpClient {
public:
MOCK_METHOD(std::string, Get, (const std::string& url), (override));
MOCK_METHOD(void, Post, (const std::string& url, const std::string& data), (override));
};
auto mockHttpClient = std::make_unique<MockHttpClient>();
UserService userService(std::move(mockHttpClient));
После такой переделки мы можем:
- скрывать детали реализации в .cc-файлах
- работать с абстрактным интерфейсом
- внедрять зависимости во время конструирования объекта
3. Внедрение зависимости через шаблоны¶
Внедрение зависимости через шаблоны использует технологию шаблонов C++ и передаёт зависимый объект как параметр шаблона в UserService. При инстанцировании шаблона указывается конкретная реализация HTTP-клиента. Этот подход разрешает зависимости на этапе компиляции, не требует вызова виртуальных функций и повышает производительность. Кроме того, класс UserService может гибко интегрироваться с различными реализациями HTTP-клиентов и легко поддаётся модульному тестированию.
В этом случае тестировать довольно просто, например:
У этого способа есть следующие преимущества:
- зависимости внедряются во время инстанцирования шаблона
- виртуальные вызовы не нужны (обратите внимание: THttpClient — не интерфейс, поэтому вызовы разрешаются на этапе компиляции)
На этом всё, что мы хотели рассказать в этом разделе. Полный код раздела выложен в сообществе — подписывайтесь и скачивайте!
Похожие темы¶
- Отказ от конструктора по умолчанию в C++ | C++那些事
- Идиома списков инициализации класса в C++ | C++那些事
- C++: классы-шаблоны вместо шаблонов функций | C++那些事
- Идиома copy-and-swap в C++ | C++那些事
- Сильнотипизированные перечисления enum class в C++ | C++那些事
- Header-Only в C++ против разделения реализации | C++那些事
- Идиома ленивых вычислений в C++ | C++那些事
- Идиома таблиц поиска в C++ | C++那些事
- Указатели на члены и информация о типах в C++ | C++那些事
- Идиома Mixin в C++ | C++那些事