Продолжая мини-серию по внедрению зависимостей (см. Мои предыдущие блоги: проблемы с DI , вспомогательные инъекции для CDI и улучшенные вспомогательные инъекции ), я взглянул на то, как обрабатывается DI в Scala.
Есть несколько подходов, один из самых интересных — это Cake Pattern. Это решение DI, использующее только функции родного языка, без какой-либо поддержки инфраструктуры. Хорошее введение можно найти в блоге Джонаса Бонера (на котором в большей степени основан этот пост) или в статье Мартина Одерски « Абстракции масштабируемых компонентов» .
Я хотел бы расширить Cake Pattern, чтобы позволить определять зависимости, которые требуют создания пользовательских данных (например, в автозаводах / ассистированных инъекциях).
Образец Торта: интерфейсы
Но давайте начнем с примера базового шаблона. Допустим, у нас есть класс User,
и что мы хотим создать службу UserRepository. Используя Cake Pattern, сначала мы создаем «интерфейс»:
У нас есть три важные вещи здесь:
- черта UserRepositoryComponent будет использоваться для выражения зависимостей. Содержит определение компонента, состоящее из:
- способ получения зависимости: метод def userRepository (также может быть val, но почему лучше def, я объясню позже)
- сам интерфейс, здесь черта UserRepository, которая дает функциональность поиска пользователей по имени пользователя
Образец Торта: реализации
Реализация компонента выглядит примерно так:
Здесь нет ничего особенного. Реализация компонента расширяет черту компонента «интерфейс». Это позволяет использовать черту UserRepository, которая может быть реализована.
Использование зависимостей
Как один компонент / услуга может сказать, что это зависит от другого? Самостоятельные аннотации Scala здесь очень полезны. Например, если компонент UserAuthorization требует UserRepository, мы можем написать это следующим образом:
Важной частью здесь является следующее: UserRepositoryComponent =>. Этим фрагментом кода мы указываем, что UserAuthorizationComponentImpl требует некоторой реализации UserRepositoryComponent. Это также переносит содержимое UserRepositoryComponent в область видимости, так что видны как метод получения пользовательского репозитория, так и сам признак UserRepository.
электропроводка
Как мы соединяем различные компоненты вместе? Опять довольно легко. Например:
Сначала нам нужно создать среду, объединив все реализации компонентов, которые мы хотим использовать, в один объект. Далее мы можем вызывать методы в среде для получения услуг.
Как насчет тестирования? Также легко:
Здесь мы смоделировали пользовательский репозиторий, чтобы мы могли тестировать UserAuthorizationComponentImpl изолированно.
defs over vals
Почему defs в определении компонента лучше в качестве способа получения зависимости? Потому что, если вы используете val, все реализации заблокированы и должны предоставить один экземпляр зависимости (константа). С помощью метода вы можете возвращать разные значения при каждом вызове. Например, в веб-среде это отличный способ реализовать область видимости! Метод может читать из запроса или состояния сеанса. Конечно, все еще можно предоставить синглтон. Или новый экземпляр зависимости от каждого вызова.
Зависимости, которые требуют пользовательских данных
Наконец, мы подошли к главному. Что если нашим зависимостям нужны данные во время выполнения? Например, если мы хотим создать службу UserInformation, которая оборачивает экземпляр User?
Ну, кто сказал, что методы, с помощью которых мы получаем зависимости, должны быть без параметров?
Разве это не лучше, чем передача экземпляра User в качестве параметра метода?
Использование Cake Pattern, создание зависимостей с состоянием, которые могут быть созданы во время выполнения с данными, предоставленными пользователем, и при этом зависеть от других компонентов, — очень просто. Это похоже на заводской метод, но с гораздо меньшим шумом.
Хорошее и плохое
Добро:
- рамки не требуются, используются только языковые функции
- тип safe — отсутствующая зависимость обнаружена во время компиляции
- мощный — «вспомогательный ввод», определение области возможно путем соответствующей реализации метода обеспечения зависимости
Плохо:
- довольно много стандартного кода: каждый компонент имеет интерфейс компонента, реализацию, интерфейс службы и реализацию службы
Однако я не думаю, что определение всех четырех частей всегда необходимо. Если есть только одна реализация компонента, вы можете объединить интерфейс компонента и реализацию в одну, и, если есть необходимость, выполнить рефакторинг позже.
Адам