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

Внедрение зависимостей (DI): зачем и как использовать

Продвинутое3 мин

Коротко

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

Суть за одну минуту

Dependency injection (DI) — приём, при котором объект получает нужные ему зависимости извне, а не создаёт их внутри себя. Чаще всего через конструктор.

Что это даёт:

  • Слабая связанность. Класс зависит от интерфейса, а не от конкретной реализации.
  • Тестируемость. В тесте вместо настоящей базы или почтового сервиса можно передать заглушку.
  • Гибкость. Чтобы сменить реализацию, достаточно изменить одно место сборки.

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

Как выглядит сильно связанный код

class OrderService {
  private db = new PostgresOrderRepository();
  private mailer = new SmtpMailer();

  async place(order: Order) {
    await this.db.save(order);
    await this.mailer.send(order.email, "Заказ принят");
  }
}

Проблемы:

  • Нельзя протестировать place, не подняв базу данных и SMTP-сервер.
  • Чтобы заменить почтовый сервис, придётся править OrderService.
  • Зависимости спрятаны внутри: по сигнатуре класса не видно, что ему нужно.

Рефакторинг: внедрение через конструктор

interface OrderRepository { save(order: Order): Promise<void>; }
interface Mailer { send(to: string, text: string): Promise<void>; }

class OrderService {
  constructor(
    private readonly repo: OrderRepository,
    private readonly mailer: Mailer,
  ) {}

  async place(order: Order) {
    await this.repo.save(order);
    await this.mailer.send(order.email, "Заказ принят");
  }
}

// Точка сборки (composition root)
const service = new OrderService(new PostgresOrderRepository(), new SmtpMailer());

Теперь OrderService ничего не знает о Postgres и SMTP. Все конкретные классы создаются в одном месте — composition root, обычно при старте приложения.

Как DI упрощает тесты

test("заказ сохраняется и отправляется письмо", async () => {
  const saved: Order[] = [];
  const sent: string[] = [];
  const service = new OrderService(
    { save: async o => { saved.push(o); } },
    { send: async to => { sent.push(to); } },
  );

  await service.place({ id: "1", email: "a@example.com" } as Order);

  expect(saved).toHaveLength(1);
  expect(sent).toEqual(["a@example.com"]);
});

Ни базы, ни сети — тест быстрый и стабильный.

DI-контейнеры

Когда классов десятки, собирать их вручную утомительно. DI-контейнер — библиотека, которая хранит правила «интерфейс → реализация» и сама создаёт объекты с нужными зависимостями, а также управляет их временем жизни.

В ASP.NET Core контейнер встроен:

builder.Services.AddScoped<IOrderRepository, PostgresOrderRepository>();
builder.Services.AddSingleton<IMailer, SmtpMailer>();
builder.Services.AddScoped<OrderService>();

public class OrderService
{
    private readonly IOrderRepository _repo;
    private readonly IMailer _mailer;

    public OrderService(IOrderRepository repo, IMailer mailer)
    {
        _repo = repo;
        _mailer = mailer;
    }
}

Основные времена жизни в .NET:

LifetimeКогда создаётся
TransientНовый экземпляр при каждом запросе зависимости
ScopedОдин экземпляр на область, в веб-приложении — на HTTP-запрос
SingletonОдин экземпляр на всё приложение

В TypeScript-мире контейнеры есть во фреймворках (например, NestJS, Angular) и в отдельных библиотеках. Но в небольшом проекте ручной сборки в composition root часто достаточно.

Частые ошибки

  • Service Locator вместо DI: класс сам достаёт зависимости из контейнера — зависимости снова скрыты.
  • Конструктор с восемью параметрами — сигнал, что класс делает слишком много.
  • Scoped-зависимость внутри Singleton — объект живёт дольше, чем должен.
  • Интерфейс на каждый класс без нужды — абстракции стоит вводить там, где реально нужна подмена.

Частые вопросы

Нет. DI — это принцип передачи зависимостей извне. Контейнер лишь автоматизирует сборку, и в небольших проектах вполне хватает ручной.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.