Scala Cake 模式:如何避免依赖冲突?

Scala Cake Pattern: How to avoid Dependency Collisions?

我的问题与Scala Cake Pattern and Dependency Collisions非常相似。但我正在努力寻找 Daniel C 的回答中建议的具体解决方案。

问题来了:

一个ProductDetailsPage(特质)需要两个独立的服务模块ProductServiceModuleSessionModule,分别由ProductServiceModuleWsSessionModuleWs实现。

两个模块都依赖于 RestServiceConfigurationProvider

对于这个RestServiceConfigurationProvider,只有一个具体的实现可用:DefaultRestServiceConfigurationProvider (atm)。

另一方面,DefaultRestServiceConfigurationProvider 取决于 RestEndpointProvider,它可以是 HybrisEndpointProviderProductServiceEndpointProvider

简而言之,ProductServiceModuleWsSessionModuleWs 连接到远程 RESTful 网络服务。特定服务的确切 IP 地址由 RestEndpointProvider 的实现提供。

现在,这就是碰撞发生的地方。请随意尝试下面的代码。发生依赖冲突的麻烦地方用注释标记。

没错,编译器会抱怨 RestEndpointProvider 的两个相互冲突的实现,即 HybrisEndpointProviderProductServiceEndpointProvider

正如丹尼尔在他的回答中提到的,为了避免任何此类冲突,我应该分别连接 ProductServiceModuleWsSessionModuleWs,每个都有自己具体的 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 !!!!! 
   }
}

隐式作用域使您可以在一定程度上控制获取值的位置。

在某个地方,您将 select 通过名称在 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]}"
  }
}