Перейти к содержанию

Последнее обновление: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-клиентов и легко поддаётся модульному тестированию.

template <typename THttpClient>
class UserService {
 private:
  THttpClient m_httpClient;
};

В этом случае тестировать довольно просто, например:

UserService<MockHttpClient> userService;

У этого способа есть следующие преимущества:

  • зависимости внедряются во время инстанцирования шаблона
  • виртуальные вызовы не нужны (обратите внимание: THttpClient — не интерфейс, поэтому вызовы разрешаются на этапе компиляции)

На этом всё, что мы хотели рассказать в этом разделе. Полный код раздела выложен в сообществе — подписывайтесь и скачивайте!

Похожие темы

Комментарии