Перейти к основному содержимому

Зависьте от абстракций

Принцип инверсии зависимостей гласит: высокоуровневый код — ваша бизнес-логика — не должен зависеть от низкоуровневых деталей, таких как конкретная база данных или формат файла. Обе стороны должны зависеть от абстракции: в TypeScript это интерфейс.

«Инверсия» — это направление стрелки. Вместо того чтобы сервис указывал на одно конкретное хранилище, сервис указывает на интерфейс — и каждое хранилище указывает на тот же интерфейс с другой стороны.


Жёстко привязан к деталям

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

Сервис, приваренный к своему хранилищу

UserService сам создаёт внутри себя DiskStore. Высокоуровневая логика регистрации теперь зависит от низкоуровневой детали — файла на диске.

Prompt
Напишите программу на TypeScript, демонстрирующую нарушение принципа инверсии зависимостей: UserService, чей метод register использует конкретный DiskStore, созданный внутри сервиса, и выводит сообщение о том, что пользователь записан в файл на диске. Зарегистрируйте одного пользователя.
Вывод: программа на TypeScript и захваченный вывод

Инвертируйте зависимость

Решение: сервис запрашивает интерфейс UserStorage в своём конструкторе и никогда не узнаёт, какую именно реализацию получил. И дисковое хранилище, и хранилище в памяти реализуют этот интерфейс.

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

Сервис принимает интерфейс

Та же логика регистрации, два разных хранилища. new UserService(new DiskStore()) или new UserService(new MemoryStore()) — код самого сервиса не меняется.

Prompt
Инвертируйте зависимость в TypeScript: определите интерфейс UserStorage с методом save, классы DiskStore и MemoryStore, которые его реализуют, и UserService, конструктор которого принимает любой UserStorage. Зарегистрируйте одного пользователя через дисковое хранилище и одного через хранилище в памяти, чтобы показать замену без изменения сервиса.
Вывод: программа на TypeScript и захваченный вывод
The service accepts an interface

Выгода: тестирование без диска

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

Prompt
Покажите выгоду инверсии зависимостей для тестирования в TypeScript: дайте MemoryStore массив, записывающий сохранённых пользователей, передайте его в UserService, зарегистрируйте двух пользователей и убедитесь, что хранилище в памяти содержит обоих, выведя результат проверки и сохранённых пользователей как в небольшом тесте.
Вывод: программа на TypeScript и захваченный вывод