Как вы делаете внедрение зависимостей с шаблоном Cake без жесткого кодирования?
Я только прочитал и наслаждался статьей образца Торта. Однако, на мой взгляд, одной из ключевых причин использования внедрения зависимостей является то, что вы можете изменять компоненты, используемые либо в XML-файле, либо в аргументах командной строки.
Как этот аспект DI обрабатывается с помощью шаблона Cake? Все примеры, которые я видел, связаны со смешиванием черт статически.
5 ответов
Поскольку смешивание в чертах выполняется статически в Scala, если вы хотите изменить черты, смешанные с объектом, создайте разные объекты на основе некоторого условия.
Давайте возьмем пример канонического торта. Ваши модули определены как черты, а ваше приложение построено как простой объект со множеством функций, смешанных в
val application =
new Object
extends Communications
with Parsing
with Persistence
with Logging
with ProductionDataSource
application.startup
Теперь у всех этих модулей есть хорошие объявления собственных типов, которые определяют их межмодульные зависимости, так что строка компилируется, только если все ваши межмодульные зависимости существуют, уникальны и хорошо типизированы. В частности, модуль "Постоянство" имеет собственный тип, который говорит, что все, что реализует "Постоянство", должно также реализовывать DataSource, абстрактную особенность модуля. Поскольку ProductionDataSource наследуется от DataSource, все отлично, и эта строка построения приложения компилируется.
Но что, если вы хотите использовать другой источник данных, указывающий на некоторую локальную базу данных для целей тестирования? Предположим далее, что вы не можете просто повторно использовать ProductionDataSource с другими параметрами конфигурации, загруженными из файла некоторых свойств. В этом случае вы должны определить новую черту TestDataSource, которая расширяет DataSource, и вместо этого смешать ее. Вы даже можете сделать это динамически, основываясь на флаге командной строки.
val application = if (test)
new Object
extends Communications
with Parsing
with Persistence
with Logging
with TestDataSource
else
new Object
extends Communications
with Parsing
with Persistence
with Logging
with ProductionDataSource
application.startup
Теперь это выглядит немного более многословно, чем хотелось бы, особенно если вашему приложению нужно изменять свою структуру по нескольким осям. С другой стороны, у вас обычно есть только один кусок условной логики построения, такой как в приложении (или, в худшем случае, один раз на жизненный цикл идентифицируемого компонента), поэтому, по крайней мере, боль сводится к минимуму и отгораживается от остальной части вашей логики.
Scala также является скриптовым языком. Таким образом, ваш конфигурационный XML может быть скриптом Scala. Это безопасный для типов и не отличающийся от языка.
Просто посмотрите на стартап:
scala -cp first.jar:second.jar startupScript.scala
не так сильно отличается от:
java -cp first.jar:second.jar com.example.MyMainClass context.xml
Вы всегда можете использовать DI, но у вас есть еще один инструмент.
Краткий ответ: в настоящее время Scala не имеет встроенной поддержки динамических миксинов.
Я работаю над плагином autoproxy, чтобы поддержать это, хотя в настоящее время он приостановлен до выпуска 2.9, когда у компилятора появятся новые функции, делающие его намного более легкой задачей.
В то же время, лучший способ достичь почти такой же функциональности - реализовать динамически добавленное поведение в качестве класса-оболочки, а затем добавить неявное преобразование обратно в обернутый член.
Пока плагин AutoProxy не станет доступным, одним из способов достижения эффекта является использование делегирования:
trait Module {
def foo: Int
}
trait DelegatedModule extends Module {
var delegate: Module = _
def foo = delegate.foo
}
class Impl extends Module {
def foo = 1
}
// later
val composed: Module with ... with ... = new DelegatedModule with ... with ...
composed.delegate = choose() // choose is linear in the number of `Module` implementations
Но будьте осторожны, недостатком этого является то, что это более многословно, и вы должны быть осторожны с порядком инициализации, если вы используете var
внутри черты. Другим недостатком является то, что если есть пути зависимых типов внутри Module
выше, вы не сможете использовать делегирование так легко.
Но если существует большое количество различных реализаций, которые можно варьировать, это, вероятно, будет стоить вам меньше кода, чем перечисление случаев со всеми возможными комбинациями.
В Lift есть что-то в этом роде. Это в основном в Scala-коде, но у вас есть некоторый контроль во время выполнения. http://www.assembla.com/wiki/show/liftweb/Dependency_Injection