Инверсия управления — один из популярных принципов объектно-ориентированного программирования, при помощи которого можно снизить связанность между компонентами, а так же повысить модульность и расширяемость ПО.
Реализуется инверсия управления несколькими способами, среди которых есть внедрение зависимостей (Dependency Injection, DI) и поиск зависимостей (Dependency Lookup, DL), о которых речь пойдёт в этой статье.
В современных реалиях разработки бизнес-ориентированного программного обеспечения, когда оно разрабатывается не раз и навсегда, а разрабатывается, поддерживается и изменяется с течением времени, сильная связанность может серьёзно осложнять процесс разработки. По этой причине во многих современных фреймворках применяются те или иные реализации инверсии управления.
Для начала давайте взглянем на пример кода без применения инверсии управления:
Как видно из этого кода, класс TodoService самостоятельно создаёт объект класса TodoRepository. Это образует сильную связь между классами TodoService и TodoRepository. Если в классе TodoService потребуется использование другого класса вместо TodoRepository, то придётся вносить соответствующие изменения в него. Даже если из TodoRepository выделить интерфейс, связанность между классами слабее не станет, так как TodoService самостоятельно создаёт объект TodoRepository.
Применение инверсии управления предполагает, что объект класса TodoRepository должен быть создан за пределами класса TodoService, но в дальнейшем должен быть передан объекту этого класса.
Внедрение зависимостей
Первый способ это сделать — применить внедрение зависимостей. Внедрение зависимостей реализуется несколькими способами, среди которых можно выделить:
- Внедрение через конструктор
- Внедрение через set-метод
- Внедрение через интерфейс
Пример внедрения зависимости через конструктор:
Внедрение зависимостей через конструктор позволяет внедрять зависимости для final-свойств, а так же внедрять сразу несколько зависимостей.
Пример внедрения зависимости через set-метод:
Пример внедрения зависимости через интерфейс:
В качестве примера внедрения зависимостей через интерфейс можно рассматривать применение интерфейсов BeanFactoryAware и ApplicationContextAware в Spring Framework.
Кроме этих способов существует и внедрение зависимостей через свойства:
Примером такого внедрения можно считать использование аннотаций @Autowired в Spring Framework или @Inject в JavaEE.
Поиск зависимостей
В случае с поиском зависимостей класс должен самостоятельно реализовывать логику получения зависимостей извне. Для этого он должен иметь доступ к некоему источнику зависимостей.
В приведённом выше примере объект TodoRepository внедряется при помощи поиска зависимостей, а DependencyProvider — при помощи внедрения зависимостей через интерфейс. Кроме этого в примере применена ещё одна техника инверсии управления — шаблон проектирования «фабрика», который реализуется классом DependencyProvider.
Что выбрать?
Оба описанных способа позволяют снизить связанность между компонентами, но поиск зависимостей предполагает, что компонент, для которого он применяется, должен реализовывать логику получения необходимых зависимостей. Это далеко не всегда удобно, соответственно, рекомендуется применять первый способ — внедрение зависимостей, там где это возможно.
Но в некоторых случаях использование поиска зависимостей всё же может быть более выгодно, например, при реализации шаблона проектирования «команда».