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

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

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

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


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

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

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

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

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

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

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

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

Сервис принимает протокол

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

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

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

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

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