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

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

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

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


Приварено к деталям

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

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

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

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

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

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

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

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

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

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

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

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

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