Принцип инверсии зависимости - 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;

    // другие свойства и методы
}

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

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

46 views·2 shares