Принцип инверсии зависимости — SOLID в деталях

Пятым и последним принципом в списке принципов SOLID является принцип инверсии зависимости (Dependency Inversion Principle; DIP), который Роберт Мартин в книге «Чистая архитектура» формулирует следующим образом: «Код, реализующий высокоуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали. Напротив, детали должны зависеть от политики«.

Оригинальная формулировка принципа состоит из двух частей:

  • Высокоуровневые модули не должны ничего импортировать из низкоуровневых модулей. И те и другие должны зависеть от абстракций (например, интерфейсов)
  • Абстракции не должны зависеть от деталей. Детали (конкретные реализации) должны зависеть от абстракций.

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

Уровни компонентов

Самые низкоуровневые компоненты в случае с приложениями на платформе Java — это различные клиентские библиотеки, которые помогают нам взаимодействовать с какими-то сторонними сервисами и приложениями: базами данных, очередями сообщений, серверами кэша и т.д.

Чуть выше уровнем стоят фреймворки, предоставляющие типовые решения для тех или иных задач. К ним можно отнести Spring Framework и Hibernate.

Выше идут компоненты нашего приложения, позволяющие взаимодействовать с этими фреймворками и библиотеками, таковыми в данном случае являются TaskController и TaskRepository.

На самом верху данного примера находится TaskService, так как он реализует самые высокоуровневую логику — бизнес-логику, характерную для деятельности предприятия, для которого разработано приложение.

В действительности уровней может быть ещё больше. Если вы добавите в ваш проект практики, характерные для предметно-ориентированного проектирования (Domain-Driven Design; DDD), то выше уровнями добавятся сервисы бизнес-логики, классы-сущности и агрегаты.

Применение принципа

Если мы представим себе «классическое» в общих чертах клиент-серверное приложение без соблюдения принципа инверсии зависимости, то картина будет следующая:

Как видим, в данном случае все компоненты зависят друг от друга, то есть от конкретных классов, а не от абстракций. Это первое нарушение принципа инверсии зависимости. Второе нарушение заключается в зависимости TaskService от TaskRepository, так как TaskService — более высокоуровневый компонент, чем TaskRepository.

В коде описанная схема будет выглядеть следующим образом:

Самая главная проблема этого кода — высокая связанность между компонентами, из-за чего код получается негибким, и, например, не получится добавить кэширование между TaskService и TaskRepository без внесения изменений в уже существующий код при помощи шаблона проектирования «декоратор».

Применение принципа инверсии зависимости к иерархии классов, продемонстрированной выше, изменит её следующим образом:

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

В результате связанность между компонентами снижается, а код проекта становится более гибким, позволяя применять шаблоны проектирования для расширения поведения без внесения изменений в существующий код.

Полезные ссылки