Чем DI-контейнер отличается от Service Locator?
Порождающие паттерны
Оба отделяют создание зависимостей от использования, но DI-контейнер сам передаёт зависимости в класс (push), а через Service Locator класс запрашивает их сам (pull) — и тем самым прячет, что ему нужно.
DI-контейнер
- Регистрация: токен или интерфейс → реализация + время жизни (singleton, scoped, transient).
- Контейнер строит граф и внедряет зависимости через конструктор; класс о контейнере не знает.
- Примеры: NestJS, Angular, InversifyJS.
Service Locator
- Глобальный реестр, у которого класс внутри методов просит
locator.get('clock'). - Зависимости не видны в сигнатуре, чтобы их узнать, надо читать тело класса.
- Незарегистрированный сервис всплывает в рантайме в момент вызова, а не при сборке графа на старте.
- В тестах подменяют глобальное состояние вместо передачи мока в конструктор.
/** Контракт Clock: { now(): Date } */
// DI: зависимость видна в конструкторе
class InvoiceService {
#clock;
constructor(clock) { this.#clock = clock; }
issue() { return { date: this.#clock.now() }; }
}
// Service Locator: зависимость спрятана внутри
const registry = new Map();
class HiddenInvoiceService {
issue() {
const clock = registry.get('clock');
return { date: clock.now() };
}
}Где локатор допустим
В composition root и инфраструктуре фреймворка: контейнер по сути и есть локатор, беда начинается, когда его вызывает бизнес-код.
Коротко для собеседования
Разница в направлении: при DI зависимости приходят в конструктор и видны в сигнатуре, а локатор класс опрашивает сам внутри методов. Локатор допустим в composition root, но в бизнес-коде он прячет зависимости и переносит ошибки в рантайм; для небольших проектов хватает pure DI без контейнера.