Перейти к содержимому

Dependency injection что это

  • автор:

Dependency Injection: анти-паттерны

Dependency Injection (DI) — это набор паттернов и принципов разработки програмного обеспечения, которые позволяют писать слабосвязный код. По мнению М.Фаулера, DI является разновидностью более глобального принципа инверсии управления (IoC), также известного как “Hollywood Principle”. Между тем, границы принципов внедрения зависимости достаточно размыты. Невозможно провести действительно четкую границу между этим и другими принципами написания качественного объектно-ориентированного кода. Например, принцип Dependency Inversion из SOLID, который часто путают с Dependency Injection, как бы подразумевает внедрение зависимостей, но им не ограничивается.

Как для любых паттернов и принципов, для DI существуют анти-паттерны. Ниже я их перечислю (с несколько вольным переводом англоязычных названий на русский язык).

Анти-паттерн №1. «Control Freak». Руководитель-наркоман.

Все зависимости контролируются напрямую. Противоположность принципу инверсии управления.
Антипаттерн возникает всегда, когда мы внутри класса явно создаем изменяемую зависимость, используя ключевое слово new. Класс, который не отпускает контроль над своими зависимостями, является Control Freak.

Пример такого класса показан ниже:

private readonly ProductRepository repository; public ProductService()

Вариант избежать данного анти-паттерна: использовать Dependency Injection (желательно через конструктор). Корректный пример после рефакторинга, не являющийся Control Freak, выглядит следующим образом:

private readonly ProductRepository repository; public ProductService(ProductRepository repository)

Анти-паттерн №2. «Bastard Injection». Внебрачная зависимость.

Данный пункт подразумевает множественные перегрузки конструкторов вместе с «конструкторами по умолчанию», которые повсеместно встречаются в .Net, включая BCL. Основная проблема в том, что внешние зависимости обычно определены в других модулях и подобные конструкторы увеличивают связность системы буквально на пустом месте. Следует избегать внебрачных внешних зависимостей везде где возможно, а разрешение зависимостей отдать DI-контейнеру. Пример анти-паттерна приведен ниже:

private readonly ProductRepository repository; public ProductService() : this(ProductService.CreateDefaultRepository()) < >public ProductService(ProductRepository repository) < if (repository == null) throw new ArgumentNullException("repository"); this.repository = repository; >private static ProductRepository CreateDefaultRepository()

Как вариант избежать данного анти-паттерна, предлагается использовать Dependency Injection. Желательно опять же через конструктор, т.к. внедрение зависимостей через конструктор является наиболее корректным способом DI.

Анти-паттерн №3. «Constrained Construction». Ограниченное построение.

Анти-паттерн возникает, если существует требование ко всем зависимостям иметь «особенный» конструктор. Данное требование зачастую проистекает из желания «однотипно создавать через Reflection». Что-то типа:

string connectionString = ConfigurationManager.ConnectionStrings["Context"].ConnectionString; string productRepositoryTypeName = ConfigurationManager.AppSettings["ProductRepositoryType"]; var productRepositoryType = Type.GetType(productRepositoryTypeName, true); var repository = (ProductRepository)Activator.CreateInstance(productRepositoryType, connectionString); 

Следует избегать данного анти-паттерна, а для однотипного создания классов использовать фабрики.

Анти-паттерн №4. «Service Locator». Сервис-локатор.

Анти-паттерн возникает при гранулированном получении отдельных сервисов в различных частях кода.
Автор перечисленных DI-анти-паттернов признает, что вопрос сервис-локатора дискуссионный, однако продолжает считать Service Locator анти-паттерном, аргументируя в основном тем, что бизнес-логика не должна знать об инфраструктурных вещах, одной из которых является сервис-локатор и все зависимости должны пробрасываться явно.

Список анти-паттернов DI приведен из книги “Dependency Injection in .NET”, за авторством Mark Seemann.

  • паттерны
  • анти-паттерны
  • dependency injection
  • Программирование
  • .NET
  • Проектирование и рефакторинг

Dependency Injection Container — JS: Предметно-ориентированное проектирование

Каждый раз, когда в коде встречается подобная запись, мы уже можем сделать вывод, что полиморфизм включения обошёл этот код стороной. Подменить реализацию UserRepository не представляется возможным, ведь он прямо жестко закодирован в месте своего использования. Такая ситуация не всегда является проблемой, но, всё же, хотелось бы иметь возможность лёгкой подмены компонентов системы. Да и в тестах часто бывает нужно подменять реализации и использовать стабы.

В этой ситуации мы можем воспользоваться DIP (dependency inversion principle), то есть принципом инверсии зависимостей:

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Хотя он и звучит страшно, на практике, особенно в динамических языках, применять его проще простого. Грубо говоря, всё сводится к тому, что мы передаём зависимости снаружи, а клиентский код ими пользуется. Например так:

const createUser = (userData, userRepository) =>  const user = new User(userData); userRepository.save(user); > 

Как видите, теперь код не зависит от конкретной реализации репозитория. Так мы получаем преимущества от ООП. Только всегда будьте прагматиками. Инверсия ради инверсии, это так себе обоснование для усложнения. В реальности не так часто бывает нужна подмена, как об этом кричат в некоторых книжках, но всё же это важная тема.

Рядом с DIP всегда появляется словосочетание Dependency Injection или Внедрение Зависимостей — это набор способов, с помощью которых можно доставить зависимости. Кроме внедрения через параметры функции, выделяют следующие способы:

  • Через конструктор
  • Через сеттер

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

Многие действительно так и делают, более того, экосистемы некоторых языков подталкивают к таким подходам. Например, в Ruby очень часто объекты делаются глобальными переменными.

Из этой ситуации есть несколько хорошо изученных выходов.

Service Locator

Сервис-локатор (service locator) — это чуть более продвинутая альтернатива глобальным переменным. Этот паттерн подразумевает наличие одного глобального объекта, который и является сервис-локатором. В начале программы он инициализируется всеми нужными сервисами. В процессе жизни программы каждый компонент сам запрашивает у локатора нужные зависимости. Честно скажем, что этот подход так себе, но за неимением лучшего, может стать неплохим подспорьем.

import locator from './locator.js' const sendEmail = (subject, body) =>  const email = new Email(subject, body); locator.emailSender.send(email); > 

DI Container

Самый продвинутый вариант называется Dependency Injection Container. При таком подходе контейнер становится центральной частью системы. С одной стороны он предоставляет интерфейс для описания всех сервисов и их зависимостей, с другой стороны сам занимается созданием графа объектов, попутно внедряя зависимости в те места, где они нужны. Такой подход особенно распространён в таких языках как Java или C#. Возможно, вы даже слышали такое название, как Spring Framework.

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

class EmailService  constructor(sender)  this.sender = sender; > sendEmail(subject, body)  const email = new Email(subject, body); this.sender.send(email); > > 

Как видите, класс не запрашивает никаких зависимостей сам, они внедряются через конструктор какой-то внешней системой.

bottlejs

bottlejs — это библиотека, которая позиционирует себя как DI Micro Container. В отличие от своих старших собратьев, она очень простая. С ее помощью достаточно легко собирать зависимости:

const bottle = new Bottle(); // Регистрация сервиса bottle.service('emailSender', EmailSender); bottle.service('emailService', 'EmailSender'); // Обращение к сервису // Возвращает объект, а не класс const  emailService > = bottle.container; emailService.sendEmail('title', 'boby'); 

За сценой Bottlejs выполняет создание объектов и инъекцию нужных зависимостей в соответствии с конфигурацией. Пример выше работает так:

const  emailService > = bottle.container; // Где-то внутри const emailService = new EmailService(new EmailSender()); 

Такой способ работы подходит в простых случаях, когда зависимости это объекты без конфигурации. В более сложных случаях понадобится метод factory() :

bottle.factory('emailSender', (container) =>  const  emailSender > = container; // Что-то делаем с emailSender // Добавляем любую логику return new EmailSender(emailSender); >); 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Простой способ внедрения зависимостей

Java-университет

Простой способ внедрения зависимостей - 1

Внедрение зависимостей или инъекция зависимостей (Dependency injection, DI) – непростая для понимания концепция, а её применение к новым или уже существующим приложениям – задача еще более запутанная. Джесс Смит покажет вам, как осуществлять внедрение зависимостей без контейнера внедрения на языках программирования C# и Java. В этой статье я покажу вам, как внедрять зависимости (DI) в .NET- и Java-приложениях. Концепция внедрения зависимостей впервые появилась в поле зрения разработчиков в 2000 году, когда Роберт Мартин написал статью «Принципы и паттерны проектирования» (позднее получивших известность под аббревиатурой SOLID). Буква D в SOLID относится к инверсии зависимостей (Dependency of Inversion, DOI), которую позднее стали называть внедрением зависимостей. Изначальное и чаще всего встречающееся определение: инверсия зависимостей — это инверсия способа управления зависимостями базовым классом. В исходной статье Мартина использовался следующий код, иллюстрирующий зависимость класса Copy от более низкоуровневого класса WritePrinter :

 void Copy()

Первая очевидная проблема: если изменить список или типы параметров метода WritePrinter , нужно внедрить обновления везде, где есть зависимость от этого метода. Этот процесс повышает затраты на обслуживание и является потенциальным источником новых ошибок.

Интересно читать о Java? Вступайте в группу Java Developer!

Другая проблема: класс Copy перестает быть потенциальным кандидатом на повторное использование. Например, что делать, если вам понадобится вывести вводимые с клавиатуры символы в файл вместо принтера? Для этого можно модифицировать класс Copy следующим образом (синтаксис языка C++):

 void Copy(outputDevice dev)

Несмотря на появление новой зависимости WriteDisk , ситуация не улучшилась (а скорее ухудшилась), поскольку был нарушен другой принцип: «программные сущности, то есть, классы, модули, функции и так далее, должны быть открыты для расширения, но закрыты для изменения». Мартин поясняет, что эти новые условные операторы if/else понижают стабильность и гибкость кода. Решение состоит в инверсии зависимостей, чтобы методы записи и чтения зависели от класса Copy . Вместо «выталкивания» зависимостей, они передаются через конструктор. Переделанный код выглядит следующим образом:

 class Reader < public: virtual int Read() = 0; >; class Writer < public: virtual void Write(char) = 0; >; void Copy(Reader& r, Writer& w)

Теперь класс Copy можно легко использовать повторно с различными реализациями методов классов Reader и Writer . У класса Copy нет никакой информации о внутреннем устройстве типов Reader и Writer , благодаря чему возможно их переиспользование с различными реализациями. Но если всё это кажется вам какой-то абракадаброй, возможно, ситуацию прояснят приведенные ниже примеры на языках Java и C#.

Пример на языках Java и C#

Для иллюстрации простоты внедрения зависимостей без контейнера зависимостей, начнем с простого примера, который можно переделать под использование DI всего за несколько шагов. Допустим, у нас есть класс HtmlUserPresentation , который, при вызове его методов, формирует пользовательский HTML-интерфейс. Вот простой пример:

 HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation(); String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status"); 

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

 public interface IHtmlUserPresentation < String createTable(ArrayList rowVals, String caption); String createTableRow(String tableCol); // Оставшиеся сигнатуры >

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

 IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation(); String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status"); 

Создание интерфейса позволяет нам легко использовать другие реализации типа IHtmlUserPresentation . Например, если мы хотим протестировать этот тип, то легко можем заменить базовый тип HtmlUserPresentation на другой тип, под названием HtmlUserPresentationTest . Выполненные до сих пор изменения упрощают тестирование, обслуживание и масштабирование кода, но ничего не делают для переиспользования, поскольку все использующие тип HtmlUserPresentation классы все еще знают о его существовании. Чтобы убрать эту прямую зависимость, можно передавать интерфейсный тип IHtmlUserPresentation в конструктор (или список параметров метода) класса или метод, который его будет использовать:

 public UploadFile(IHtmlUserPresentation htmlUserPresentation) 

У конструктора UploadFile теперь есть доступ ко всей функциональности типа IHtmlUserPresentation , но он ничего не знает о внутреннем устройстве реализующего этот интерфейс класса. В данном контексте, внедрение типа происходит при создании экземпляра класса UploadFile . Интерфейсный тип IHtmlUserPresentation становится переиспользуемым, передавая различные реализации различным классам или методам, для которых необходима разная функциональность.

Заключение и рекомендации для закрепления материала

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

Dependency Injection в Spring

Внедрение зависимости (DI — Dependency injection) — это процесс предоставления программному компоненту внешней зависимости. Согласно принципу единой обязанности, объект передает «заботу» о построении нужных ему зависимостей внешнему механизму, который специально для этого предназначен.

Каковы достоинства применения Dependency Injection и как DI реализуется в Spring?

Screenshot_4-1801-f279ae.png

Основные плюсы внедрения зависимости следующие: • сокращается объем связующего кода. Это одно из самых главных преимуществ, так как существенно уменьшается объем кода, который надо написать для связывания между собой разных компонентов приложения. Чаще всего этот код весьма прост — в процессе создания зависимости должен создаваться новый экземпляр соответствующего объекта; • упрощается конфигурация приложения. Благодаря применению Dependency Injection, процесс конфигурирования приложения существенно облегчается. В целях конфигурирования классов, которые можно внедрять в другие классы, используют аннотации либо файлы XML; • появляется возможность управлять общими зависимостями в одном репозитории. Когда речь идет о традиционном подходе к управлению зависимостями в общих службах (удаленные службы, транзакция, подключение к источнику данных и т. п.), разработчик создает экземпляры зависимостей (либо получает их из фабричных классов) там, где они действительно нужны, то есть внутри зависимого класса. Результат — распространение зависимостей по множеству классов в программном приложении, что, как известно, способно затруднить их изменение. Если же использовать DI, то вся информация об общих зависимостях находится в одном репозитории (в Spring можно хранить данную информацию в XML-файлах либо Java-классах), что значительно упрощает процесс управления зависимостями, и, как следствие, уменьшает число возможных ошибок; • улучшается возможность тестирования. Если классы проектируются для DI, появляется возможность простой замены зависимостей. Это весьма полезно при тестировании; • стимулируются качественные проектные решения. Собственно говоря, проектирование для Dependency Injection — это проектирование с применением интерфейсов. Используя Spring, разработчик получает в свое распоряжение широкий спектр DI-средств, поэтому он может сосредоточить свои силы на построении логики программного приложения, а не на платформе, поддерживающей DI.

Screenshot_3-1801-e14fa2.png

Реализация DI в Spring Framework

Реализация в Spring основана на 2-х ключевых Java-концепциях: интерфейсах и компонентах JavaBean. Используя Spring в качестве поставщика DI, разработчик получает гибкость определения конфигурации зависимостей внутри приложений разными путями: — внешне в XML-файлах; — посредством конфигурационных Java-классов Spring; — с помощью Java-аннотаций в коде).

Компоненты JavaBean (их еще называют Plain Old Java Object (POJO) — старый добрый объект Java) предоставляют стандартный механизм, позволяющий создавать Java-ресурсы, конфигурируемыми множеством способов. Благодаря применению Dependency Injection, объем кода, необходимого при проектировании программного приложения на основе интерфейсов, уменьшается почти до нуля. Вдобавок к этому, посредством интерфейсов вы можете получить наибольшую отдачу от DI, ведь бины способны использовать для удовлетворения их зависимости любую реализацию интерфейса.

Типы реализации

К типам реализации DI в Spring относятся: Constructor Dependency Injection. Зависимости компонента предоставляются ему в его конструкторе (конструкторах).

Screenshot_1-1801-8d6b97.png

Setter Dependency Injection. IoC-контейнер внедряет зависимости компонента в компонент, используя методы установки в стиле JavaBean.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *