Использование Dependency Injection является почти стандартом при разработке программного обеспечения. Тем не менее, во многих случаях может показаться, что использование шаблона подразумевает использование контейнера / инфраструктуры DI. Но действительно ли нужны рамки?
Для реализации DI все, что вам действительно нужно, это удалить new
s из вашего кода и перенести зависимости в конструктор. Конечно, должно быть какое-то место, где создаются объекты; для этого вам нужен верхний уровень («основной» класс), где вся проводка сделана. Вот где DI-контейнеры действительно помогают: они снимают утомительную задачу передачи правильных параметров конструкторам. Обычно это делается во время выполнения с помощью отражения.
MacWire использует другой подход. Основываясь на объявлениях, указывающих, какие классы должны быть созданы, он генерирует код, необходимый для создания нового экземпляра класса, с правильными параметрами, взятыми из включающего типа. Это делается во время компиляции с использованием макроса Scala . Затем код проверяется типом с помощью компилятора Scala, поэтому весь процесс является типобезопасным, и если зависимость отсутствует, вы сразу об этом узнаете (в отличие от традиционных контейнеров DI).
Например, учитывая:
class DatabaseAccess() class SecurityFilter() class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) class UserStatusReader(userFinder: UserFinder) trait UserModule { import com.softwaremill.macwire.MacwireMacros._ lazy val theDatabaseAccess = wire[DatabaseAccess] lazy val theSecurityFilter = wire[SecurityFilter] lazy val theUserFinder = wire[UserFinder] lazy val theUserStatusReader = wire[UserStatusReader] }
Сгенерированный код будет:
trait UserModule { lazy val theDatabaseAccess = new DatabaseAccess() lazy val theSecurityFilter = new SecurityFilter() lazy val theUserFinder = new UserFinder(theDatabaseAccess, theSecurityFilter) lazy val theUserStatusReader = new UserStatusReader(theUserFinder) }
Классы , которые должны быть подключены должны содержаться в Scala trait
, class
или object
(контейнер образует «модуль»). MacWire ищет значения по включающему типу (признак / класс / объект) и по любым супер-признакам / классам. Следовательно, возможно объединение нескольких модулей с использованием наследования.
В настоящее время поддерживаются две области; зависимость может быть одноэлементной (объявленной как val
/ lazy val
), или для каждого использования может быть создан новый экземпляр (объявленный как def
).
Обратите внимание, что этот подход очень гибкий; все, с чем мы здесь имеем дело, это обычный код Scala, поэтому, если класс нужно создать каким-то особым образом, ничто не мешает нам просто записать его в виде кода. Кроме того, wire[T]
может быть вложен в тело метода, и он будет расширен и до создания нового экземпляра.
Для интеграционного тестирования модуля, если для некоторых классов мы хотели бы использовать макет, достаточно простого переопределения, например:
trait UserModuleForTests extends UserModule { override lazy val theDatabaseAccess = mockDatabaseAccess override lazy val theSecurityFilter = mockSecurityFilter }
Проект является продолжением моего предыдущего поста в блоге . Существует также аналогичный проект для Java, который использует процессоры аннотаций: Dagger .
MacWire доступен в Maven Central . Чтобы использовать, просто добавьте эту строку в вашу сборку SBT:
libraryDependencies += "com.softwaremill.macwire" %% "core" % "0.1"
Код на GitHub , под лицензией Apache2, так что не стесняйтесь использовать, вилку и исследовать. Взгляните на README, которая содержит больше информации.
Планы на будущее включают поддержку фабрик и поиск параметров по именам, поддержку значений конфигурации и других областей, таких как области запросов или сессий, которые очень полезны для веб-проектов.