Зачем использовать шаблон торта scala, а не абстрактные поля? - PullRequest
25 голосов
/ 10 августа 2011

Я читал о том, как делать инъекцию зависимостей в скале через шаблон торта .Я думаю, что понимаю это, но я, должно быть, что-то пропустил, потому что все еще не вижу смысла в этом!Почему предпочтительнее объявлять зависимости с помощью собственных типов, а не просто абстрактных полей?

Учитывая пример в Программирование Scala TwitterClientComponent объявляет такие зависимости, используя шаблон торт:

//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
        }

Я уверен, что мне чего-то не хватает в превосходстве торта!Однако в настоящий момент я не вижу, что он предлагает для объявления зависимостей каким-либо другим способом (конструктор, абстрактные поля).

Ответы [ 4 ]

8 голосов
/ 10 августа 2011

Черты с аннотацией собственного типа гораздо более компонуемы, чем старые фасоли с инжекцией поля, которые вы, вероятно, имели в виду во втором фрагменте.

Давайте посмотрим, как вы будете создавать эту черту:*

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), они будут автоматически разрешены компилятором.

7 голосов
/ 10 августа 2011

Подумайте, что произойдет, если TwitterService использует TwitterLocalCache. Было бы намного проще, если бы TwitterService самостоятельно набрал TwitterLocalCache, потому что TwitterService не имеет доступа к объявленному вами val localCache. Шаблон Cake (и самопечатание) позволяет нам вводить инъекции более универсальным и гибким способом (среди прочего, конечно).

1 голос
/ 11 октября 2013

В этом примере я думаю, что нет большой разницы. Самостоятельные типы могут принести больше ясности в случаях, когда признак объявляет несколько абстрактных значений, таких как

trait ThreadPool {
  val minThreads: Int
  val maxThreads: Int
}

Тогда вместо зависимости от нескольких абстрактных значений вы просто объявляете зависимость от ThreadPool. Самостоятельные типы (используемые в паттерне Cake) для меня - это просто способ объявить несколько абстрактных членов одновременно, дав им удобное имя.

1 голос
/ 10 августа 2011

Я не был уверен, как будет работать фактическое подключение, поэтому я адаптировал простой пример в записи блога, на который вы ссылались, используя абстрактные свойства, как вы предлагали.

// =======================  
// 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 

в этом простом случае это работает (поэтому предложенная вами техника действительно применима).

Однако в том же блоге показаны как минимум три других метода для достижения того же результата; Я думаю, что выбор в основном о читабельности и личных предпочтениях. В случае с техникой, которую вы предлагаете, ИМХО класс Warmer плохо сообщает о своем намерении ввести зависимости. Кроме того, чтобы связать зависимости, мне пришлось создать еще две черты (PotSensorMixin и HeaterMixin), но, возможно, у вас был лучший способ сделать это.

...