Зачем использовать шаблон scala cake, а не абстрактные поля?
Я читал о том, как делать инъекцию зависимостей в scala с помощью шаблона торта. Думаю, я это понимаю, но я, должно быть, что-то пропустил, потому что я до сих пор не вижу смысла в этом! Почему предпочтительнее объявлять зависимости через собственные типы, а не только абстрактные поля?
Учитывая пример в Программирование Scala TwitterClientComponent
объявляет зависимости, подобные этому, с использованием шаблона cake:
//other trait declarations elided for clarity
...
trait TwitterClientComponent {
self: TwitterClientUIComponent with
TwitterLocalCacheComponent with
TwitterServiceComponent =>
val client: TwitterClient
class TwitterClient(val user: TwitterUserProfile) extends Tweeter {
def tweet(msg: String) = {
val twt = new Tweet(user, msg, new Date)
if (service.sendTweet(twt)) {
localCache.saveTweet(twt)
ui.showTweet(twt)
}
}
}
}
Как это лучше, чем объявлять зависимости как абстрактные поля, как показано ниже?
trait TwitterClient(val user: TwitterUserProfile) extends Tweeter {
//abstract fields instead of cake pattern self types
val service: TwitterService
val localCache: TwitterLocalCache
val ui: TwitterClientUI
def tweet(msg: String) = {
val twt = new Tweet(user, msg, new Date)
if (service.sendTweet(twt)) {
localCache.saveTweet(twt)
ui.showTweet(twt)
}
}
}
Глядя на время инстанцирования, которое происходит, когда на самом деле происходит DI (как я понимаю), я изо всех сил стараюсь увидеть преимущества пирога, особенно когда вы рассматриваете дополнительную клавиатуру, которую вы должны использовать для заявлений на торты (охватывающая черта )
//Please note, I have stripped out some implementation details from the
//referenced example to clarify the injection of implemented dependencies
//Cake dependencies injected:
trait TextClient
extends TwitterClientComponent
with TwitterClientUIComponent
with TwitterLocalCacheComponent
with TwitterServiceComponent {
// Dependency from TwitterClientComponent:
val client = new TwitterClient
// Dependency from TwitterClientUIComponent:
val ui = new TwitterClientUI
// Dependency from TwitterLocalCacheComponent:
val localCache = new TwitterLocalCache
// Dependency from TwitterServiceComponent
val service = new TwitterService
}
Теперь снова с абстрактными полями, более или менее одинаковыми!:
trait TextClient {
//first of all no need to mixin the components
// Dependency on TwitterClient:
val client = new TwitterClient
// Dependency on TwitterClientUI:
val ui = new TwitterClientUI
// Dependency on TwitterLocalCache:
val localCache = new TwitterLocalCache
// Dependency on TwitterService
val service = new TwitterService
}
Я уверен, что я должен что-то пропустить о превосходстве торта! Однако на данный момент я не вижу, что он предлагает, чтобы объявить зависимости каким-либо другим способом (конструктор, абстрактные поля).
Ответы
Ответ 1
Черты с аннотацией самонастройки гораздо более сложны, чем старая fasioned beans с инъекцией поля, что вы, вероятно, имели в виду во втором фрагменте.
Посмотрите, как вы начнете использовать эту черту:
val productionTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with TwitterConnection
Если вам нужно проверить этот признак, вы, вероятно, пишете:
val testTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with MockConnection
Хм, немного СУХОЕ нарушение. Пусть улучшится.
trait TwitterSetup extends TwitterClientComponent with TwitterUI with FSTwitterCache
val productionTwitter = new TwitterSetup with TwitterConnection
val testTwitter = new TwitterSetup with MockConnection
Кроме того, если у вас есть зависимость между службами в вашем компоненте (например, пользовательский интерфейс зависит от TwitterService), они будут автоматически разрешены компилятором.
Ответ 2
Подумайте, что произойдет, если TwitterService
использует TwitterLocalCache
. Было бы намного проще, если TwitterService
был введен автоматически в TwitterLocalCache
, потому что TwitterService
не имеет доступа к объявленному val localCache
. Шаблон Cake (и самонастраивающийся) позволяет нам вводить гораздо более универсальный и гибкий способ (в частности, конечно).
Ответ 3
Я не знал, как будет работать фактическая проводка, поэтому я применил простой пример в записи блога, который вы связали с использованием абстрактных свойств, как вы предполагали.
// =======================
// service interfaces
trait OnOffDevice {
def on: Unit
def off: Unit
}
trait SensorDevice {
def isCoffeePresent: Boolean
}
// =======================
// service implementations
class Heater extends OnOffDevice {
def on = println("heater.on")
def off = println("heater.off")
}
class PotSensor extends SensorDevice {
def isCoffeePresent = true
}
// =======================
// service declaring two dependencies that it wants injected
// via abstract fields
abstract class Warmer() {
val sensor: SensorDevice
val onOff: OnOffDevice
def trigger = {
if (sensor.isCoffeePresent) onOff.on
else onOff.off
}
}
trait PotSensorMixin {
val sensor = new PotSensor
}
trait HeaterMixin {
val onOff = new Heater
}
val warmer = new Warmer with PotSensorMixin with HeaterMixin
warmer.trigger
в этом простом случае он работает (поэтому предложенная техника действительно применима).
Однако один и тот же блог показывает, по крайней мере, еще три метода для достижения того же результата; Я думаю, что выбор в основном касается читаемости и личных предпочтений. В случае с методикой, которую вы предлагаете IMHO, класс Warmer плохо сообщает о намерении вводить зависимости. Также для подключения зависимостей мне пришлось создать еще две черты (PotSensorMixin и HeaterMixin), но, возможно, у вас был лучший способ сделать это.
Ответ 4
В этом примере я думаю, что нет большой разницы. Самостоятельные типы потенциально могут обеспечить большую ясность в случаях, когда черта объявляет несколько абстрактных значений, например
trait ThreadPool {
val minThreads: Int
val maxThreads: Int
}
Затем вместо зависимости от нескольких абстрактных значений вы просто объявляете зависимость от ThreadPool.
Самостоятельные типы (как используется в шаблоне Cake) для меня - это всего лишь способ объявить сразу несколько абстрактных членов, давая им удобное имя.