Фабрики полезны, когда нам нужно создать несколько экземпляров класса во время выполнения, обычно предоставляя некоторые параметры, но все же без new
явного использования ; мы хотим сделать сложное создание объекта абстрактным. Созданный объект может зависеть как от предоставляемых во время выполнения данных (параметров), так и от некоторых других «сервисов».
MacWire 0.3 добавляет поддержку для некоторых сценариев использования фабрик, в то время как другие могут быть реализованы в чистом Scala.
TL; DR:
- MacWire 0.3 добавляет поддержку для проводки, используя параметры метода, включающего
wire[]
макрос - традиционные подходы к определению инъекционных фабрик многословны
- но используя вложенные классы в Scala, можно определить фабрики простым, компактным и читаемым способом.
В качестве рабочего примера нашей целью будет создание оболочки для User
объекта домена. Мы хотим обернуть пользователя в PriceCalculator
, который рассчитывает цены на продукты с учетом пользовательских скидок. Чтобы прочитать скидки, калькулятор должен получить доступ к базе данных, используя экземпляр DatabaseAccess
класса.
Следовательно, в идеале мы хотели бы иметь частично проводную PriceCalculator
сеть, которая имеет доступ к базе данных и которая может быть легко создана для любого данного пользователя. Как реализовать это с помощью Scala и MacWire?
ЗАВОДЫ В МОДУЛЯХ
Во-первых, фабрики могут быть частями «модулей», которые содержат проводные экземпляры. В таком случае вместо a val
мы должны использовать a def
.
Новая функция в MacWire 0.3 заключается в том, что параметры метода включения также используются для подключения.
Например:
case class User class DatabaseAccess class PriceCalculator(databaseAccess: DatabaseAccess, user: User) trait ShoppingModule extends Macwire { lazy val databaseAccess = wire[DatabaseAccess] def priceCalculator(user: User) = wire[PriceCalculator] }
В этом случае макрос расширит код до:
trait ShoppingModule extends Macwire { lazy val databaseAccess = new DatabaseAccess def priceCalculator(user: User) = new PriceCalculator(databaseAccess, user) }
Обратите внимание, что это также будет работать, если модуль был вложен в def; следовательно, мы можем создавать целые графы объектных объектов, используя параметры метода для подключения («подмодули»).
Поскольку на фабриках параметры часто представляют данные, может быть несколько параметров одного типа (включая примитивы). Вот почему при подключении с использованием параметров метода включения, если найдено несколько совпадений для типа, добавляется дополнительный шаг сопоставления по имени.
ИНЪЕКЦИОННЫЕ ЗАВОДЫ
Это хорошо работает в модулях, но что если нам нужно передать фабрику в качестве параметра другому классу? Например, если у нас есть SpecialOfferMailer
, что нужно создать для каждого пользователя PriceCalculator
?
Мы могли бы передать объект функции и объявить зависимость следующим образом:
class SpecialOfferMailer(priceCalculator: User => PriceCalculator)
Это также можно рассматривать как частично примененный PriceCalculator
конструктор.
Это может хорошо работать в простых случаях, но имеет три недостатка:
- нам нужно повторять всю сигнатуру функции всякий раз, когда мы объявляем зависимость (однако здесь может помочь псевдоним типа)
- мы теряем имена параметров, которые могут вызвать проблемы с читаемостью кода, если наша фабрика принимает примитивы или несколько параметров одного типа
- для автоматического преобразования
def
s в функциональные объекты потребуется специальная поддержка MacWire , или отдельный функциональный объект val придется определять вручную.
Мы также могли бы пойти по традиционному пути определения отдельной фабричной черты, например:
class PriceCalculator(databaseAccess: DatabaseAccess, user: User) object PriceCalculator { trait Factory { def create(user: User): PriceCalculator } } class SpecialOfferMailer(priceCalculatorFactory: PriceCalculator.Factory) { ... }
тогда мы могли бы также иметь некоторую (еще не реализованную) поддержку для подключения таких фабрик с использованием MacWire, например:
trait ShoppingModule { lazy val priceCalculatorFactory = wireFactory[PriceCalculator, PriceCalculator.Factory] ... }
который расширится до:
trait ShoppingModule { lazy val priceCalculatorFactory = new PriceCalculator.Factory { // the wire here uses values from the method parameters and from the // outer module def create(user: User) = wire[PriceCalculator] } ... }
Однако, опять же, это имеет недостатки:
- довольно многословный — отдельная черта
- нужна специальная поддержка для автоматического подключения
- нам нужно повторить заводские параметры в
create
def и в конструкторе класса
SCALA FACTORIES
Однако есть и другой способ — используя классы case немного нетипичным образом, мы получим элегантную фабричную реализацию. Например:
class PriceCalculatorFactory(databaseAccess: DatabaseAccess) { case class create(user: User) { // methods } } class SpecialOfferMailer(priceCalculatorFactory: PriceCalculatorFactory) // Nothing special here. Just plain ol' MacWire trait ShoppingModule extends Macwire { lazy val databaseAccess = wire[DatabaseAccess] lazy val priceCalculatorFactory = wire[PriceCalculatorFactory] lazy val specialOfferMailer = wire[SpecialOfferMailer] }
Обратите внимание, что при таком подходе параметры службы и параметры данных хорошо разделены (первый — это параметры основного класса, последний — параметры вложенного класса наблюдения). Также определение списка параметров не повторяется! И нам не нужна какая-либо специальная поддержка для автоматического подключения.
Поскольку вложенный класс является классом case, использование create
выглядит как вызов метода, хотя на самом деле это конструктор нового объекта, например:
class SpecialOfferMailer(...) { def mailOfferOfTheDay(user: User) { val priceCalculator = priceCalculatorFactory.create(user) products.foreach { product => mailOffer(user, product, priceCalculator.price(product)) } } }
Тип каждого пользователя объекта цена калькулятор PriceCalculatorFactory#create
. Это немного уродливо, особенно если нужно передать его, но мы можем добавить, например, к объекту пакета псевдоним типа:
type PriceCalculator = PriceCalculatorFactory#create
Исходники для MacWire доступны на GitHub под лицензией Apache2; и бинарный релиз находится в центральном хранилище.
Веселитесь, и дайте мне знать, что вы думаете о внедрении Scala-factory!