Принцип инверсии зависимости - SOLID в деталях
Пятым и последним принципом в списке принципов SOLID является принцип инверсии зависимости (Dependency Inversion Principle; DIP), который Роберт Мартин в книге "Чистая архитектура" формулирует следующим образом: "Код, реализующий высокоуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали. Напротив, детали должны зависеть от политики".
Оригинальная формулировка принципа состоит из двух частей:
- Высокоуровневые модули не должны ничего импортировать из низкоуровневых модулей. И те и другие должны зависеть от абстракций (например, интерфейсов)
- Абстракции не должны зависеть от деталей. Детали (конкретные реализации) должны зависеть от абстракций.
Принцип инверсии зависимости говорит о том, что при разработке программного обеспечения важно опираться на абстракции, а не на конкретные реализации, так как любое изменение в интерфейсе или абстрактном классе будет приводить к изменениям в конечных реализациях, но не каждое изменение в конечной реализации потребует изменений в интерфейсе или абстрактном классе. И именно в том, что компоненты высокоуровневых и низкоуровневых модулей должны зависеть от абстракций, а не друг от друга, и заключается инверсия зависимости.
Уровни компонентов
Самые низкоуровневые компоненты в случае с приложениями на платформе Java - это различные клиентские библиотеки, которые помогают нам взаимодействовать с какими-то сторонними сервисами и приложениями: базами данных, очередями сообщений, серверами кэша и т.д.
Чуть выше уровнем стоят фреймворки, предоставляющие типовые решения для тех или иных задач. К ним можно отнести Spring Framework и Hibernate.
Выше идут компоненты нашего приложения, позволяющие взаимодействовать с этими фреймворками и библиотеками, таковыми в данном случае являются TaskController и TaskRepository.
На самом верху данного примера находится TaskService, так как он реализует самые высокоуровневую логику - бизнес-логику, характерную для деятельности предприятия, для которого разработано приложение.
В действительности уровней может быть ещё больше. Если вы добавите в ваш проект практики, характерные для предметно-ориентированного проектирования (Domain-Driven Design; DDD), то выше уровнями добавятся сервисы бизнес-логики, классы-сущности и агрегаты.
Применение принципа
Если мы представим себе "классическое" в общих чертах клиент-серверное приложение с многоуровневой архитектурой без соблюдения принципа инверсии зависимости, то картина будет следующая:
Как видим, в данном случае все компоненты зависят друг от друга, то есть от конкретных классов, а не от абстракций. Это первое нарушение принципа инверсии зависимости. Второе нарушение заключается в зависимости TaskService от TaskRepository, так как TaskService - более высокоуровневый компонент, чем TaskRepository.
В коде описанная схема будет выглядеть следующим образом:
class TaskController {
private TaskService taskService;
// другие свойства и методы
}
class TaskService {
private TaskRepository taskRepository;
// другие свойства и методы
}
class TaskRepository {
private EntityManager entityManager;
// другие свойства и методы
}Самая главная проблема этого кода - высокая связанность между компонентами, из-за чего код получается негибким, и, например, не получится добавить кэширование между TaskService и TaskRepository без внесения изменений в уже существующий код при помощи шаблона проектирования "декоратор".
Применение принципа инверсии зависимости к иерархии классов, продемонстрированной выше, изменит её следующим образом:
После применения принципа инверсии зависимости между компонентами нет прямых связей, так как теперь они зависят от абстракций, а код должен выглядеть следующим образом:
class TaskController {
private TaskService taskService;
// другие свойства и методы
}
interface TaskService {
// методы
}
class TaskServiceImpl implements Taskservice {
private TaskRepository taskRepository;
// другие свойства и методы
}
interface TaskRepository {
// методы
}
class TaskRepositoryImpl implements TaskRepository {
private EntityManager entityManager;
// другие свойства и методы
}В результате связанность между компонентами снижается, а код проекта становится более гибким, позволяя применять шаблоны проектирования для расширения поведения без внесения изменений в существующий код.
Полезные ссылки
- Статья на сайте
- Поддержать автора: стать доном или подкинуть звонкую монету в Boosty или Tinkoff
