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

Зачем использовать @Singleton над объектом Scala в Play Framework?

Я использую Играть! Рамки для Scala уже почти год. В настоящее время я использую версию 2.5.x.

Я знаю об эволюции контроллеров в Play и о том, как разработчики были вынуждены покинуть статические маршруты object.

Я также знаю об использовании Guice в игре.

Если вы загрузите activator и запустите:

activator new my-test-app play-scala

Активатор создаст для вас шаблонный проект. Мой вопрос определенно находится вокруг этого файла этого шаблона.

мой тест-приложение/приложения/услуги/Counter.scala

package services

import java.util.concurrent.atomic.AtomicInteger
import javax.inject._

/**
 * This trait demonstrates how to create a component that is injected
 * into a controller. The trait represents a counter that returns a
 * incremented number each time it is called.
 */
trait Counter {
  def nextCount(): Int
}

/**
 * This class is a concrete implementation of the [[Counter]] trait.
 * It is configured for Guice dependency injection in the [[Module]]
 * class.
 *
 * This class has a `Singleton` annotation because we need to make
 * sure we only use one counter per application. Without this
 * annotation we would get a new instance every time a [[Counter]] is
 * injected.
 */
@Singleton
class AtomicCounter extends Counter {  
  private val atomicCounter = new AtomicInteger()
  override def nextCount(): Int = atomicCounter.getAndIncrement()
}

Вы также можете увидеть его использование в файле this:

мой тест-приложение/приложение/контроллеры/CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._
import services.Counter

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController @Inject() (counter: Counter) extends Controller {

  /**
   * Create an action that responds with the [[Counter]] current
   * count. The result is plain text. This `Action` is mapped to
   * `GET /count` requests by an entry in the `routes` config file.
   */
  def count = Action { Ok(counter.nextCount().toString) }

}

Это означает, что каждый контроллер, имеющий конструктор @Inject() (counter: Counter), получит тот же экземпляр Counter.

Итак, мой вопрос:

Зачем использовать @Singleton и затем @Inject его в контроллер, когда для этого примера вы можете просто использовать объект Scala?
Это намного меньше кода.

Пример:

мой тест-приложение/приложения/услуги/Counter.scala

package services

trait ACounter {
  def nextCount: Int
}

object Counter with ACounter {
  private val atomicCounter = new AtomicInteger()
  def nextCount(): Int = atomicCounter.getAndIncrement()
}

Используйте его так:

мой тест-приложение/приложение/контроллеры/CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._

import services.{Counter, ACounter}

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController extends Controller {
  //depend on abstractions
  val counter: ACounter = Counter

  def count = Action { Ok(counter.nextCount().toString) }

}

В чем разница? Является ли инъекция предпочтительной, и почему?

4b9b3361

Ответ 1

Является ли инъекция предпочтительным способом? Обычно да

Пара преимуществ использования инъекции зависимостей:

  • Отключить контроллер от конкретной реализации Counter.
    • Если вы должны использовать object, вам придется сменить контроллер, чтобы указать на другую реализацию. EG Counter2.nextCount().toString
  • Вы можете изменять реализацию во время тестирования с помощью пользовательских привязок Guice
    • Предположим, что внутри Counter вы выполняете вызов WS. Это может привести к некоторому тестированию на уровень сложности. Если вы используете инъекцию зависимостей с помощью Guice, вы можете переопределить привязку между Counter и AtomicCounter, чтобы указать на автономную версию Counter, которую вы написали специально для своих тестов. См. здесь для получения дополнительной информации об использовании тестов Guice for Play.

Также см. мотивации, которые игра имела для перехода на DI.

Я говорю вообще, потому что я видел, что инъекция зависимостей ужасно ошибочна с использованием Spring и других фреймворков Java. Я бы сказал, что вы должны использовать свое собственное мнение, но ошибаться на стороне использования DI для Play.

Ответ 2

Возможно, потому что объект Scala singleton не может иметь параметры? Например, если у вас есть класс обслуживания, в который введен DAO, и вы хотите использовать службу в контроллере, вы должны ввести его. Самый простой способ (IMO) - это DI с Guice... Кроме того, вы можете иметь свои зависимости в одном месте (модуле) и т.д.

Ответ 3

Я не уверен, если я понимаю ваш вопрос, но инъекция предпочтительна, потому что:

  • различные части вашего приложения менее связаны
  • проще заменить свою зависимость на другой класс, предоставляя ту же функциональность (в случае, если вам нужно будет это делать в будущем) - вам нужно будет изменить несколько строк кода и не искать все вхождения вашего объекта
  • Проще проверить (особенно, когда вам нужно что-то издеваться)

Вкратце: D от принципов SOLID: "Зависит от абстракций. Не зависит от конкреций".