Что такое dependency injection java

Краткое введение во внедрение зависимостей: что это и когда это необходимо использовать

Что такое dependency injection java. Смотреть фото Что такое dependency injection java. Смотреть картинку Что такое dependency injection java. Картинка про Что такое dependency injection java. Фото Что такое dependency injection java

Nov 4, 2018 · 4 min read

Что такое dependency injection java. Смотреть фото Что такое dependency injection java. Смотреть картинку Что такое dependency injection java. Картинка про Что такое dependency injection java. Фото Что такое dependency injection java

Введение

В разработке программного обеспечения, внедрение зависимостей это такая техника, где посредством одного объекта (или статического метода) предоставляются зависимости другого объекта. Зависимость — это объект, который может быть использован (как сервис).

Это определение из Википедии (в версии на английском языке — прим. переводчика), и все же, его не так и просто понять. Давайте разберемся с этим получше.

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

Зависимость или зависимое — означает полагаться на что-то. Это все равно что, если сказать, что мы слишком много полагаемся на мобильные телефоны — это означает, что мы зависим от них.

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

Когда класс A использует некоторую функциональность из класса B, тогда говорят, что класс A зависим от класса B.

Что такое dependency injection java. Смотреть фото Что такое dependency injection java. Смотреть картинку Что такое dependency injection java. Картинка про Что такое dependency injection java. Фото Что такое dependency injection java

В Java, прежде чем мы сможем использовать методы других классов, нам необходимо для начала создать экземпляры этого класса (то есть класс А должен создать экземпляр класса В).

Таким образом, передавая задачу создания объекта чему-то другому и прямое использование этой зависимости называется внедрением зависимостей.

Что такое dependency injection java. Смотреть фото Что такое dependency injection java. Смотреть картинку Что такое dependency injection java. Картинка про Что такое dependency injection java. Фото Что такое dependency injection java

Так почему следует использовать внедрение зависимостей?

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

Перед вами класс Car, отвечающий за создание всех объектов зависимостей. Теперь, что если мы решим избавиться колес компании MRFWheels и хотим использовать колеса от Yokohama в будущем?

Нам нужно будет воссоздать объект класса Car с новой зависимостью от Yokohama. Но при использовании внедрении зависимостей мы можем изменить колеса во время выполнения программы (потому что зависимости можно внедрять во время выполнения, а не во время компиляции).

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

Это делает наш класс автомобилей независимым от создания объектов таких как колеса, аккумулятор и т.д.

Существует три основных типа внедрения зависимостей:

Внедрение зависимостей ответственно за:

Если есть какие-либо изменения в объектах, то DI смотрит на него, и он не должен относиться к классу с использованием этих объектов.

Таким образом, если объекты будут меняться в будущем, тогда ответственность DI заключается в предоставлении соответствующих объектов классу.

Инверсия управления — концепция, лежащая в основе внедрения зависимости

Это означает, что класс не должен конфигурировать свои зависимости статистически, а должен быть сконфигурирован другим классом извне.

Это пятый принцип S.O.L.I.D из пяти основных принципов объектно-ориентированного программирования и разработки от дяди Боба, в котором говорится, что класс должен зависеть от абстракции, а не от чего-то конкретного (простыми словами, жестко закодированного).

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

Преимущества использования внедрения зависимостей

Недостатки использования внедрения зависимостей

Тем не менее вы вполне можете реализовать внедрение зависимостей самостоятельно без использования сторонних библиотек и фреймворков или используя их.

Библиотеки и фреймворки, реализующие внедрение зависимостей

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

Источник

Dependency injection в Java EE 6

В рамках JSR-299 “Contexts and Dependency Injection for the Java EE platform” (ранее WebBeans) была разработана спецификация описывающая реализацию паттерна внедрения зависимости, включенная в состав Java EE 6. Эталонной реализацией является фреймворк Weld, о котором и пойдет речь в данной статье.

К сожалению в сети не так много русскоязычной информации о нем. Скорее всего это связано с тем, что Spring IOC является синонимом dependency injection в Java Enterprise приложениях. Есть конечно еще Google Guice, но он тоже не так популярен.

В статье хотелось бы рассказать об основных преимуществах и недостатках Weld.

Немного теории

В начале стоит упомянуть про JSR-330 “Dependency Injection for Java” спецификацию, разработанную инженерами из SpringSource и Google, определяющую базовые механизмы для реализации DI в Java приложениях. Как и Spring и Guice, Weld использует аннотации предусмотренные данной спецификацией.

Weld может работать не только с Java EE приложениями, но и с обычным окружением Java SE. Естественно есть поддержка Tomcat, Jetty; в официальной документации описаны подробные инструкции по настройке.

В Contexts and Dependency Injection (CDI) невозможно инъектировать бин через его имя в виде строки, как это например делается в Spring через Qualifier. Вместо этого используется шаблон qualifier annotations (о котором ниже). С точки зрения создателей CDI, это более typesafe подход, позволяющий избежать некоторых ошибок и обеспечивающий гибкость DI.

Для того, чтобы задействовать CDI нужно создать файл beans.xml в директории WEB-INF для веб-приложения (или в META-INF для Java SE):

@ConversationScoped представляет собой определенный промежуток времени взаимодействия пользователя с конкретной вкладкой в браузере. Поэтому он чем-то похож на жизненный цикл сессии, но важное отличие в том, что старт “сессии” задается вручную. Для этого объявлен интерфейс javax.enterprise.context.Conversation, который определяет методы start(), end(), а также setTimeout(long), для закрытия сессии по истечении времени.

Конечно же, есть и Singleton, который находится в пакете javax.inject, т.к. является частью спецификации JSR-330. В реализации CDI данного скоупа есть одна особенность: в процессе инъекции клиент получает ссылку на реальный объект созданный контейнером, а не proxy. В результате чего могут быть проблемы неоднозначности данных, если состояние синглтона будет меняться, а использующие его бины, например, были или будут сериализованы.

Для создания своего скоупа нужно написать аннотацию и отметить ее @ScopeType, а также реализовать интерфейс javax.enterprise.context.spi.Context.

Небольшая путаница может возникнуть с тем, что в пакете javax.faces.bean также находятся аннотации для управления скоупом managed бинов JSF. Связано это с тем, что в JSF приложениях использование CDI не обязательно: действительно, ведь можно обойтись стандартными инъекциями с помощью @EJB, @PersistenceContext и т.п. Однако, если мы хотим использовать продвинутые штуки из DI, удобней применять аннотации из JSR-299 и 330.

Примерчик

Допустим есть сервис проверяющий логин и пароль пользователя.

Напишем его реализацию:

Теперь добавим контроллер, который будет использовать сервис логина:

Как видно из примера, в этом случае для инъекции с помощью Weld необходимо добавить аннотацию Inject в нужном поле: контейнер найдет все возможные реализации интерфейса, выберет подходящую и создаст объект привязанный к скоупу контроллера. Естественно поддерживаются инъекции в метод и конструктор. В примере также используется аннотация Named, она служит для того, чтобы к бину можно было обращаться в EL по имени.

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

Теперь после редеплоя приложения в консоли возникнет ошибка:

Если в точке инъекции подходят несколько реализаций, то Weld бросает исключение. Разработчики CDI предусмотрели разрешение такой проблемы. Для этого отметим StubLoginService аннотацией Alternative:

Теперь данная реализация недоступна для инъекций и после редеплоя ошибка не возникнет, однако сейчас Weld делает не совсем то, что нам нужно. Добавим следующее в beans.xml:

Таким образом мы подменили реализацию при старте приложения. Чтобы вернуть рабочую версию достаточно закомментировать строчку в файле настройке бинов.

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

Теперь нужно сообщить Weld, что в точке инъекции необходимо подставить именно Md5LoginService. Для этого воспользуемся qualifier annotations. Сама идея очень проста: когда контейнер решает какую именно реализацию нужно внедрить, он проверяет аннотации в точке инъекции и аннотации у возможных реализаций. Проверяемые аннотации называются спецификаторами (qualifier). Спецификатор это обычная java аннотация, которая дополнительно аннотирована javax.inject.Qualifier:

Теперь в контроллере проаннатируем поле в которое будет совершена подстановка, а также реализацию Md5LoginService:

Теперь Weld подставит нужную реализацию. Конечно возникает вопрос: каждый раз писать новую аннотацию?! В большинстве случаев так и придется сделать, но эта плата за typesafe. К тому же аннотации можно агрегировать, например так:

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

Т.е. для рассматриваемого примера мы добавили новое поле c типом перечисления, указывающий какой именно это алгоритм хеширования. Пусть и пришлось написать новую аннотацию и перечисление, зато теперь практически невозможно ошибиться с выбором нужной реализации, к тому же увеличилась читабельность кода. Также нужно всегда помнить, что если реализация не имеет спецификатора, то он добавляется по умолчанию javax.enterprise.inject.Default.

Правда после проделанных манипуляций StubLoginService перестал подставляться в поле. Это связано с тем, что у него нет спецификатора Hash, поэтому Weld даже не рассматривает его как возможную реализацию интерфейса. Для решения этой проблемы есть одна хитрость: аннотация @Specializes, которая заменяет реализацию другого бина. Чтобы указать Weld какую именно реализацию нужно заменить, нужно ее просто расширить:

Представим, что у нас появились новые требования: при попытке входа пользователя в систему, нужно попытаться проверить пароль всеми возможными алгоритмами реализованными в системе. Т.е. нам нужно перебрать все реализации интерфейса. В Spring такая задача решается через подстановку в коллекцию обобщенную по нужному интерфейсу. В Weld для этого можно использовать интерфейс javax.enterprise.inject.Instance и встроенный спецификатор Any. Пока отключим альтернативные реализации и посмотрим, что получится:

Аннотация Any говорит о том, что нам все равно какие спецификаторы могут быть у реализаций. Интерфейс Instance реализует Iterable, поэтому с ним можно делать такие красивые штуки через foreach. Вообще этот интерфейс предназначен не только для этого. Он содержит перегруженный метод select(), который позволяют в runtime выбирать нужную реализацию. В качестве параметров он принимает экземпляры аннотаций. В целом сейчас это реализовано несколько “необычно”, поскольку приходится создавать анонимные классы (или создавать отдельно, только для того чтобы использовать в одном месте). Частично это решается абстрактным классом AnnotationLiteral от которого можно расшириться и обобщить по нужной аннотации. Помимо этого в Instance есть специальные методы isUnsatisfied и isAmbiguous, с помощью которых можно в runtime проверить есть ли подходящая реализация и только потом получить ее экземпляр через метод get(). Выглядит это примерно так:

Понятно, что в данном случае можно было пройтись циклом по loginServiceInstance, как это сделали в примере выше, и найти нужную реализацию по getClass().equals(), но тогда при изменении реализаций пришлось править код и в этом месте тоже. Weld представляет более гибкий и безопасный подход, пусть немного добавляя новых абстракций для изучения.

Как уже было отмечено выше, Weld при выборе нужной реализации руководствуется и типом и спецификатором. Но в некоторых случаях, например со сложной иерархией наследования, мы можем указать тип реализации в ручную, используя аннотацию @Typed.

Это все конечно хорошо, но что делать когда нам нужно создать экземпляр класса каким-то хитрым способом? Опять же Spring в xml контексте предлагает богатый набор тегов для инициализации свойств у объектов, создания списков, мап и т.д. У Weld для этого есть всего одна аннотация @Produces, которой отмечаются методы и поля генерирующие объекты (в том числе и скалярных типов). Перепишем наш предыдущий пример:

Теперь укажем через спецификатор откуда хотим получить реализацию:

Вот собственно и все. Источником может быть и обычное поле. Правила подстановки теже самые. Более того в метод buildLoginService можно также инъектировать бины:

Как видите модификатор доступа никак не влияет. Скоуп объектов генерируемых buildLoginService не привязан к скоупу бина, в котором он объявлен, поэтому в данном случае он будет Dependent. Чтобы это изменить достаточно добавить аннотацию к методу, например так:

Помимо этого можно вручную освобождать ресурсы генерируемые с помощью @Produces. Для этого, Вы не поверите, есть другая аннотация @Disposed, которая работает примерно так:

Когда жизненный цикл объекта подходит к концу, Weld ищет методы удовлетворяющие типу и спецификатору метода генаратора, а также помеченные @Disposed и вызывает его.

Заключение

Мы рассмотрели далеко не все возможности JSR-299. Помимо этого есть ряд дополнительных спецификаторов, механизмы управления жизненным циклом бинов внутри контейнера (interceptors, decorators), стереотипы, событийная модель, с помощью которой удобно организовывать сложную бизнес логику и еще много-много приятных мелочей.

В данной статье не хотелось противопоставлять Weld другим dependency injection фреймворкам, о которых говорилось в начале. Weld самодостаточен и обладает интересной реализацией, достойной внимания Java Enterprise разработчиков.

Источник

Dependency injection

От переводчика

Представляемый вашему вниманию перевод открывает серию статей от Jakob Jenkov, посвященных внедрению зависимостей, или DI. Примечательна серия тем, что в ней автор, анализируя понятия и практическое применение таких понятий как «зависимость», «внедрение зависимостей», «контейнер для внедрения зависимостей», сравнивая паттерны создания объектов, анализируя недостатки конкретных реализаций DI-контейнеров (например, Spring), рассказывает, как пришел к написанию собственного DI-контейнера. Таким образом, читателю предлагается познакомиться с довольно цельным взглядом на вопрос управления зависимостями в приложениях.

В данной статье сравнивается подход к настройке объектов изнутри и извне (DI). По смыслу настоящая статья продолжает статью Jakob Jenkov Understanding Dependencies, в которой дается определение самому понятию «зависимости» и их типам.

Что такое dependency injection java. Смотреть фото Что такое dependency injection java. Смотреть картинку Что такое dependency injection java. Картинка про Что такое dependency injection java. Фото Что такое dependency injection java

Серия включает в себя следующие статьи

Внедрение зависимостей

«Внедрение зависимостей» — это выражение, впервые использованное в статье Мартина Фаулера Inversion of Control Containers and the Dependency Injection Pattern. Это хорошая статья, но она упускает из виду некоторые преимущества контейнеров внедрения зависимостей. Также я не согласен с выводами статьи, но об этом — в следующих текстах.

Объяснение внедрения зависимостей

Внедрение зависимостей — это стиль настройки объекта, при котором поля объекта задаются внешней сущностью. Другими словами, объекты настраиваются внешними объектами. DI — это альтернатива самонастройке объектов. Это может выглядеть несколько абстрактно, так что посмотрим пример:

UPD: после обсуждения представленных автором фрагментов кода с flatscode и fogone, я принял решение скорректировать спорные моменты в коде. Изначальный замысел был в том, чтобы не трогать код и давать его таким, каков он написан автором. Оригинальный авторский код в спорных местах закомментирован с указанием «в оригинале», ниже дается его исправленная версия. Также оригинальный код можно найти по ссылке в начале статьи.

Этот DAO (Data Access Object), MyDao нуждается в экземпляре javax.sql.DataSource для того, чтобы получить подключения к базе данных. Подключения к БД используются для чтения и записи в БД, например, объектов Person.

Заметьте, что класс MyDao создает экземпляр DataSourceImpl, так как нуждается в источнике данных. Тот факт, что MyDao нуждается в реализации DataSource, означает, что он зависит от него. Он не может выполнить свою работу без реализации DataSource. Следовательно, MyDao имеет «зависимость» от интерфейса DataSource и от какой-то его реализации.

Класс MyDao создает экземпляр DataSourceImpl как реализацию DataSource. Следовательно, класс MyDao сам «разрешает свои зависимости». Когда класс разрешает собственные зависимости, он автоматически также зависит от классов, для которых он разрешает зависимости. В данном случае MyDao завсист также от DataSourceImpl и от четырех жестко заданных строковых значений, передаваемых в конструктор DataSourceImpl. Вы не можете ни использовать другие значения для этих четырех строк, ни использовать другую реализацию интерфейса DataSource без изменения кода.

Как вы можете видеть, в том случае, когда класс разрешает собственные зависимости, он становится негибким в отношении к этим зависимостям. Это плохо. Это значит, что если вам нужно поменять зависимости, вам нужно поменять код. В данном примере это означает, что если вам нужно использовать другую базу данных, вам потребуется поменять класс MyDao. Если у вас много DAO-классов, реализованных таким образом, вам придется изменять их все. В добавок, вы не можете провести юнит-тестирование MyDao, замокав реализацию DataSource. Вы можете использовать только DataSourceImpl. Не требуется много ума, чтобы понять, что это плохая идея.

Давайте немного поменяем дизайн:

Заметьте, что создание экземпляра DataSourceImpl перемещено в конструктор. Конструктор принимает четыре параметра, это — четыре значения, необходимые для DataSourceImpl. Хотя класс MyDao все еще зависит от этих четырех значений, он больше не разрешает зависимости сам. Они предоставляются классом, создающим экземпляр MyDao. Зависимости «внедряются» в конструктор MyDao. Отсюда и термин «внедрение (прим. перев.: или иначе — инъекция) зависимостей». Теперь возможно сменить драйвер БД, URL, имя пользователя или пароль, используемый классом MyDao без его изменения.

Внедрение зависимостей не ограничено конструкторами. Можно внедрять зависимости также используя методы-сеттеры, либо прямо через публичные поля (прим. перев.: по поводу полей переводчик не согласен, это нарушает защиту данных класса).

Класс MyDao может быть более независимым. Сейчас он все еще зависит и от интерфейса DataSource, и от класса DataSourceImpl. Нет необходимости зависеть от чего-то, кроме интерфейса DataSource. Это может быть достигнуто инъекцией DataSource в конструктор вместо четырех параметров строкового типа. Вот как это выглядит:

Теперь класс MyDao больше не зависит от класса DataSourceImpl или от четырех строк, необходимых конструктору DataSourceImpl. Теперь можно использовать любую реализацию DataSource в конструкторе MyDao.

Цепное внедрение зависимостей

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

Как вы можете видеть, теперь MyBizComponent зависит от класса DataSourceImpl и четырех строк, необходимых его конструктору. Это еще хуже, чем зависимость MyDao от них, потому что MyBizComponent теперь зависит от классов и от информации, которую он сам даже не использует. Более того, реализация DataSourceImpl и параметры конструктора принадлежат к разным слоям абстракции. Слой ниже MyBizComponent — это слой DAO.

Решение — продолжить внедрение зависимости по всем слоям. MyBizComponent должен зависеть только от экземпляра MyDao. Вот как это выглядит:

Снова зависимость, MyDao, предоставляется через конструктор. Теперь MyBizComponent зависит только от класса MyDao. Если бы MyDao был интерфейсом, можно было бы менять реализацию без ведома MyBizComponent.

Такой паттерн внедрения зависимости должен продолжается через все слои приложения, с самого нижнего слоя (слоя доступа к данным) до пользовательского интерфейса (если он есть).

Источник

Изучая Dependency Injection

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

Информации достаточно даже на хабре, но к написанию поста меня подвигло то обстоятельство, что везде обсуждается КАК сделать, но практически нигде – ЗАЧЕМ. Можно ли создать хорошую архитектуру, если вы не знаете для чего она нужна и в чем именно должна быть хороша? Можно принимать во внимание определенные принципы и явные тренды, — это поможет свести к минимуму непредвиденные проблемы, но понимать – это еще лучше.

Внедрение зависимостей — это шаблон проектирования, при котором поля или параметры создания объекта конфигурируется извне.

Зная, что многие ограничатся чтением первых абзацев, я изменила статью.
Несмотря на то, что подобное «определение» DI встречается во многих источниках — оно неоднозначное, поскольку заставляет пользователя думать, что инъекция — это нечто, что заменяет создание/инициализацию объектов, или, уж по крайней мере, очень активно участвует в этом процессе. Делать такую реализацию DI, конечно, никто не запретит. Но DI может быть пассивной оберткой вокруг создания объекта, которая обеспечивает предоставление входящих параметров. В такой реализации у нас получается еще один уровень абстракции и отличное разделение обязанностей: объект сам отвечает за свою инициализацию, а инъекция реализует хранение данных и обеспечение ими модулей приложения.

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

На мой взгляд, основная часть перемен привнесена массовым внедрением автотестирования. И для тех, кто активно пишет автотесты, данная статья очевидна как белый день, можно дальше не читать. Только вы не представляете, как много людей их не пишет. Я понимаю, что у маленьких компаний и стартапов нет не это ресурсов, но, к сожалению, и в больших компаниях часто находятся более первоочередные проблемы.

Рассуждения тут очень простые. Допустим вы тестируете функцию с параметрами a и b, и вы ожидаете получить результат x. В какой-то момент, ваши ожидания не сбываются, функция выдает результат y, и потратив некоторое время, вы обнаруживаете внутри функции синглтон, который в некоторых состояниях приводит результат выполнения функции к другому значению. Этот синглтон назвали неявной зависимостью, и всячески зареклись использовать его в подобных ситуациях. К сожалению, слов из песни не выбросишь, иначе получится уже совсем другая песня. А потому, вынесем наш синглтон как входящую переменную в функцию. Теперь у нас уже 3 входящие переменные a, b, s. Вроде все очевидно: меняем параметры – получаем однозначный результат.

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

Замечение 1. Если, учитывая критику паттерна синглтон, вы решили заменить его, ну например, на UserDefaults, то применительно к данной ситуации, вырисовывается все та же неявная зависимость.

Замечение 2. Не совсем корректно говорить, что только из-за автотестирования не стоит использовать синглтоны внутри тела функции. В целом, с точки зрения программирования не совсем правильно, что при одинаковых входящих — функция выдает разные результаты. Просто на автотестах эта проблема вырисовалась более отчетливо.

Дополним вышеуказанный пример. У вас есть объект, который содержит 9 настроек пользователя(переменных), например права на чтение/редактирование/подпись/печать/пересылку/удаление/блокировку/ исполнение/копирование документа. В вашей функции используются только три переменные из этих настроек. Что вам передавать в функцию: весь объект с 9 переменными как один параметр, или только три нужные настройки тремя отдельными параметрами? Очень часто мы укрупняем передаваемые объекты, чтобы не задавать много параметров, то есть выбираем первый вариант. Такой способ будет считаться передачей «неоправданно широких зависимостей». Как вы уже сами догадались, для целей автотестирования лучше использовать второй вариант и передавать только те параметры, которые используются.

Мы сделали 2 вывода:
— функция должна получить все необходимые параметры на входе
— функция не должна получать излишних параметров на входе

Хотели как лучше – а получили функцию с 6-тью параметрами. Предположим, что внутри функции все в порядке, но кто-то должен взять на себя работу по обеспечению входящих параметров функции. Как я уже писала, мои рассуждения схематичны. Я подразумеваю не просто обычную функцию класса, а скорее функцию инициализации/создание модуля (vip, viper, объект с данными и тп). В этом контексте перефразируем вопрос: кто должен обеспечить входящие параметры для создания модуля?

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

Отсюда рождается мысль вынести создание/инициализацию модуля в отдельную конструкцию. Тут пришло время написать несколько строк в качестве примера:

В примере есть модуль списка счетов AccountList, который вызывает модуль детальной информации по счету AccountDetail.

Для инициализации модуля AccountDetail нужны 3 переменные. Переменную account AccountDetail получает от родительского модуля, переменные permission1, permission2 впрыскиваются путем инъекции. За счет инъекции, вызов модуля с деталями счета будет выглядеть:

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

Я вынесла реализацию инъекции (сборку) в статическую функцию в расширении класса. Но реализация может быть любой на ваше усмотрение.

Существует несколько способов конфигурации:
Constructor Injection, Property injection, Interface Injection.
Для Swift:
Initializer Injection, Property Injection, Method Injection.

Наиболее распространенные — это инъекции конструктора(инициализации) и свойств.
Важно: практически во всех источниках рекомендуется отдавать предпочтение инъекции конструктора. Сравните Constructor/Initializer Injection и Property injection:

Вроде бы преимущества первого способа очевидны, но почему-то некоторые понимают инъекцию, как конфигурирование уже созданного объекта и используют второй способ. Я за первый способ:

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

Если вы хотите упорядочивать архитектуру и дальше, а также потестировать переходы и работу сборки, то придется еще немного поработать.

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

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

Это возможный пример реализации. В примере используется фреймворк Swinject, который народился не так уж давно. Swinject позволяет создать контейнер для автоматизированного управления зависимостями, а также позволяет создавать контейнеры для Storyboards. Более подробно о Swinject можно посмотреть в примерах на raywenderlich. Этот сайт мне очень нравится, но данный пример не самый удачный, поскольку рассматривает применение контейнера только в автотестах, в то время как контейнер должен быть заложен в архитектуре приложения. Вы в своем коде, можете сами написать контейнер.

На этом всем спасибо. Надеюсь вы не сильно скучали, читая этот текст.

Источник

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

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