Недавно Debasish Ghosh также написал в блоге на подобную тему . Я думаю, что его статья — очень хорошее введение в предмет.
Ниже приведены некоторые проблемы, с которыми я столкнулся с Cake Pattern. (Функции высшего порядка будут рассмотрены в следующем посте.) Если у вас есть решения по любому из них, дайте мне знать!
Параметризация системы с помощью компонента реализации
Прежде всего, невозможно параметризовать систему с помощью компонентной реализации. Предположим, у меня есть три компонента: DatabaseComponent, UserRepositoryComponent, UserAuthenticatorComponent с реализациями, среда / точка входа верхнего уровня системы будет создана следующим образом:
1
2
3
|
val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent |
Теперь, чтобы создать среду тестирования с фиктивной базой данных, мне нужно сделать:
1
2
3
|
val env = new MockDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent |
Обратите внимание, сколько кода совпадает. Это не проблема с 3 компонентами, но если есть 20? Все они, кроме одного, нужно повторить, чтобы изменить реализацию одного компонента. Это явно приводит к значительному дублированию кода.
Конфигурация компонента
Довольно часто компонент должен быть настроен. Допустим, у меня есть UserAuthenticatorComponent, который зависит от UserRepositoryComponent. Однако компонент аутентификатора имеет абстрактный val encryptionMethod, используемый для настройки алгоритма шифрования. Как я могу настроить компонент? Есть два способа. Абстрактный val может быть конкретизирован при определении env, например:
1
2
3
4
5
|
val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent { val encryptionMethod = EncryptionMethods.MD 5 } |
Но что, если я хочу повторно использовать настроенный компонент? Очевидный ответ — расширить черту UserAuthenticatorComponent. Однако, если у этого компонента есть какие-либо зависимости (которые в Cake Pattern выражаются с использованием self-типов), их необходимо повторить, так как self-типы не наследуются. Таким образом, повторно используемый сконфигурированный компонент может выглядеть так:
1
2
3
4
5
6
|
trait UserAuthenticatorComponentWithMD 5 extends UserAuthenticatorComponent { // dependency specification duplication! this : UserRepositoryComponent = > val encryptionMethod = EncryptionMethods.MD 5 } |
Если мы не будем повторять эти типы, компилятор будет жаловаться на неправильное использование UserAuthenticatorComponent.
Нет контроля над порядком инициализации
Проблема, также связанная с конфигурацией, заключается в том, что не существует безопасного типа для гарантии того, что компоненты инициализируются в правильном порядке. Предположим, как указано выше, что UserAuthenticatorComponent имеет абстрактный метод encryptionMethod, который должен быть указан при создании компонента. Если у нас есть другой компонент, который зависит от UserAuthenticatorComponent:
1
2
3
4
5
|
trait PasswordEncoderComponent { this : UserAuthenticatorComponent = > // encryptionMethod comes from UserAuthenticatorComponent val encryptionAlgorithm = Encryption.getAlgorithm(encryptionMethod) } |
и инициализировать нашу систему следующим образом:
1
2
3
4
5
6
|
val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent with PasswordEncoderComponent { val encryptionMethod = EncryptionMethods.MD 5 } |
тогда в момент инициализации алгоритма шифрования метод шифрования будет нулевым! Единственный способ предотвратить это — смешать UserAuthenticatorComponentWithMD5 перед PasswordEncoderComponent. Но проверка типов не скажет нам этого.
Pros
Не поймите меня неправильно, что мне не нравится Cake Pattern — я думаю, он предлагает очень хороший способ структурировать ваши программы. Например, это устраняет необходимость в фабриках ( которые я не очень большой поклонник ) или приятно разделяет зависимости от компонентов и зависимости от данных (*). Но все же, это могло бы быть лучше;).
(*) Здесь каждый фрагмент кода имеет фактически два типа аргументов: обычные аргументы метода, которые могут использоваться для передачи данных, и аргументы компонента, выраженные как собственный тип содержащего компонента. Должны ли эти два типа аргументов рассматриваться по-разному — хороший вопрос :).
Какой у вас опыт работы с DI в Scala? Используете ли вы инфраструктуру Java DI, один из подходов, использованных выше, или каким-либо другим способом?
Ссылка: DI в Scala: плюсы и минусы Cake Pattern от нашего партнера JCG Адама Варски в блоге Адама Варски .
- Scala Tutorial — Scala REPL, выражения, переменные, основные типы, простые функции, сохранение и запуск программ, комментарии
- Scala Tutorial — кортежи, списки, методы для списков и строк
- Scala Tutorial — условное исполнение с блоками if-else и соответствием
- Scala Tutorial — итерация, для выражений, yield, map, filter, count
- Scala Tutorial — регулярные выражения, сопоставление
- Scala Tutorial — регулярные выражения, сопоставления и замены с помощью API scala.util.matching
- Учебник по Scala — Карты, Наборы, groupBy, Параметры, Flatten, FlatMap
- Веселье с функцией композиции в Scala
- Как Scala изменил мой взгляд на мой Java-код
- Тестирование с помощью Scala
- Что такое инверсия зависимостей? Это IoC?