Scala Cake Pattern: Как избежать коллизий зависимостей?

Мой вопрос очень похож на Scala Cake Pattern и коллизии зависимостей. Но я изо всех сил пытаюсь найти конкретное решение, как предложено в ответе Дэниела С.

Так вот в чем проблема:

ProductDetailsPage (черта) требует двух независимых сервисных модулей ProductServiceModule а также SessionModule, реализованный ProductServiceModuleWs а также SessionModuleWs соответственно.

Оба модуля полагаются на RestServiceConfigurationProvider,

За это RestServiceConfigurationProviderСуществует только одна конкретная реализация: DefaultRestServiceConfigurationProvider (Атм).

DefaultRestServiceConfigurationProvider с другой стороны, зависит отRestEndpointProvider который может быть HybrisEndpointProvider или ProductServiceEndpointProvider

Короче, ProductServiceModuleWs а также SessionModuleWs подключаться к удаленным веб-сервисам RESTful. Точный IP-адрес конкретной службы обеспечивается реализацией RestEndpointProvider.

Вот где происходят столкновения. Не стесняйтесь попробовать код ниже. Проблемное место, где происходит конфликт зависимостей, отмечено комментарием.

По правде говоря, компилятор жалуется на две конфликтующие реализации RestEndpointProviderа именно HybrisEndpointProvider а также ProductServiceEndpointProvider

Как сказал Даниэль в своем ответе, чтобы избежать подобных столкновений, я должен подключить ProductServiceModuleWs а также SessionModuleWs отдельно, каждая со своей конкретной реализацией RestEndpointProvider, возможно, так

      new ProductServiceModuleWs
      with DefaultRestServiceConfigurationProvider
      with ProductServiceEndpointProvider


      new SessionModuleWs
      with DefaultRestServiceConfigurationProvider
      with HybrisEndpointProvider

Но вот где я застрял.

Как эти два отдельно настроенных модуля теперь могут быть введены в ProductDetailsPage избегать столкновений с зависимостями, но все еще использовать шаблон торт?

Вот пример кода. Код самодостаточен и должен работать в вашей IDE.

case class RestEndpoint(url: String, username: Option[String] = None,   password: Option[String] = None)


trait RestEndpointKey {
   def configurationKey: String
}

case object HybrisEndpointKey extends RestEndpointKey { val configurationKey = "rest.endpoint.hybris" }
case object ProductServiceEndpointKey extends RestEndpointKey { val configurationKey = "rest.endpoint.productservice" }


trait ProductDetailsPage {
    self: ProductServiceModule with SessionModule =>
}



trait ProductServiceModule {}

trait SessionModule {}


trait ProductServiceModuleWs extends ProductServiceModule {
    self: RestServiceConfigurationProvider =>
}


trait SessionModuleWs extends SessionModule {
    self: RestServiceConfigurationProvider =>
}


trait RestServiceConfigurationProvider {}

trait DefaultRestServiceConfigurationProvider extends    RestServiceConfigurationProvider {
    self: RestEndpointProvider =>
}


sealed trait RestEndpointProvider {
   def endpointKey: RestEndpointKey
}

trait HybrisEndpointProvider extends RestEndpointProvider {
   val endpointKey = HybrisEndpointKey
}

trait ProductServiceEndpointProvider extends RestEndpointProvider {
   val endpointKey = ProductServiceEndpointKey
}


object Example extends App {

   new ProductDetailsPage
      with ProductServiceModuleWs
      with SessionModuleWs
      with DefaultRestServiceConfigurationProvider
      with HybrisEndpointProvider
      with ProductServiceEndpointProvider /// collision, since HybrisEndpointProvider already defined the endpointKey !!!!! 
   }
}

1 ответ

Решение

Неявная область действия дает вам некоторый контроль над тем, где вы выбираете значения.

Где-то вы будете выбирать между a и b по имени, независимо от того, является ли имя термином или типом.

Если вы различаете их по типу, вы можете запросить их по типу.

Удобство в том, что вы можете установить конфиг для Config[Value1] в противном случае это было бы дополнением к пользовательскому элементу, как в вашем примере.

Как показано, вы также можете ввести импликации в лексической области.

package conflict

case class Value(s: String)

trait Value1 extends Value
object Value1 {
  implicit val v: Config[Value1] = new Config[Value1] { def value = new Value("hi") with Value1 }
}
trait Value2 extends Value
object Value2 {
  implicit val v: Config[Value2] = new Config[Value2] { def value = new Value("bye") with Value2 }
}

trait Config[A <: Value] { def value: A }

trait Configurator {
  def config[A <: Value : Config]: Config[A] = implicitly[Config[A]]
}

trait Consumer1 { _: Configurator =>
  def f = config[Value1].value
}
trait Consumer2 { _: Configurator =>
  def g = config[Value2].value
}
trait Consumer3 { _: Configurator =>
  def h[V <: Value : Config] = config[V].value
}

object Test extends App with Configurator with Consumer1 with Consumer2 with Consumer3 {
  Console println s"Using $f"
  Console println s"Using $g"
  locally {
    implicit val `my local config` = new Config[Value2] { def value = new Value("hello again") with Value2 }
    Console println s"Using ${h[Value2]}"
  }
}
Другие вопросы по тегам