Принцип инверсии зависимостей (DIP)
Принцип D — Dependency Inversion
Что такое Dependency Inversion?
Это пятый принцип SOLID. Его часто путают с DI, но это разные вещи.
Dependency Inversion Principle (DIP) гласит:
- Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Play ITЗагрузка интерактивного демо…
Как работает DI?
Представим, что у нас есть модуль А (бизнес-логика, "важная штука") и модуль Б (база данных, "техническая деталь").
КЛАСС СервисПользователей
поле база := новый Postgres() // ❌ верхний уровень знает деталь
метод Зарегистрировать(имя)
база.Сохранить(имя)
конец
КОНЕЦ
Справочно на Python (нарушение DIP)
Код ITЗагрузка примера кода…
Здесь возникает проблема:
модуль Азнает, что естьPostgresDB;- если мы захотим заменить СУБД на MySQL, то полезем внутрь
UserService; - если
PostgresDBпоменяет имя метода сsaveнаstore, то сломаетсямодуль А.
Модуль А (важный) зависит от модуля Б (деталь). Это нарушение DIP.
Для соблюдения добавляем абстракцию (интерфейс) - она не зависит ни от А, ни от Б.
ИНТЕРФЕЙС БазаДанных
метод Сохранить(данные)
КОНЕЦ
КЛАСС Postgres РЕАЛИЗУЕТ БазаДанных …
КЛАСС MySql РЕАЛИЗУЕТ БазаДанных …
КЛАСС СервисПользователей
поле база: БазаДанных
конструктор(база)
метод Зарегистрировать(имя)
база.Сохранить(имя)
конец
КОНЕЦ
Справочно на Python
Код ITЗагрузка примера кода…
Что изменилось:
модуль Абольше не знает проPostgreDBилиMySQLDB. Он знает только проDatabaseInterface;модуль Б(PostgresDB) знает проDatabaseInterfaceи реализует его;- направление зависимости развернулось - раньше
А→Б, теперь обаАиБ→ абстракция.
Было:
UserService (важный) --------> PostgresDB (деталь)
(зависит)
Стало (DIP):
DatabaseInterface (абстракция)
↑ ↑
| |
| |
UserService (важный) PostgresDB (деталь)
(зависит от абстракции) (зависит от абстракции)
Модуль А (важный) не должен знать про модуль Б (деталь).
Они оба должны знать про интерфейс, который А использует, а Б реализует.
Как этого добиться:
- Создаём интерфейс (в Python — абстрактный класс с методами).
Модуль Апринимает в конструктор любой объект, у которого есть этот интерфейс.Модуль Бреализует этот интерфейс.
Всё. Вот и весь смысл.
Пример
Представим, что у нас есть лампочка (LightBulb), которую можно включить и выключить. И у нас есть переключатель (Switch), который может управлять лампочкой. Если мы будем создавать класс Switch, то он должен установить связь с LightBulb.
Код ITЗагрузка примера кода…
Но при таком подходе (это кстати обычная связь без DIP), Switch зависит от конкретной реализации Lightbulb, и нельзя подключить другие девайсы, пока прямо их не перечислим. И если применить DIP, устанавливая задачу, чтобы Switch не зависел от конкретного вида устройство, а был более универсальным:
Код ITЗагрузка примера кода…
Здесь можно увидеть, что добавляется интерфейс Switchable (переключаемый). Класс Lighbulb наследует Switchable, и Fan тоже Switchable - словом, как лампочка, так и вентилятор - оба "переключаемые" и теоретически, к ним можно применять класс. А класс Swich изменил подход, создав себе device, у которого тип данных - Switchable.
Класс Switch (переключатель) получает поле device, которое потом используется в методе Switch, что сделало его универсальным - класс больше не зависит от конкретики, и стал более гибким, расширяемым и тестируем.
Смысл? А теперь нам не нужно будет создавать Fan и прочие устройства со своими экземплярами в Switch. Теперь мы просто при вызове конструктора Switch(Switchable device) будем передавать любой из Switchable.