Подтвердить что ты не робот

Зачем использовать шаблон 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
        }

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

4b9b3361

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