Scala蛋糕模式:如何避免依赖性冲突?

时间:2016-06-20 23:07:05

标签: scala cake-pattern

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

所以这就是问题所在:

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

两个模块都依赖于RestServiceConfigurationProvider

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

另一方面,DefaultRestServiceConfigurationProvider取决于RestEndpointProviderHybrisEndpointProviderProductServiceEndpointProvider

简而言之,ProductServiceModuleWsSessionModuleWs连接到远程RESTful Web服务。特定服务的确切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 !!!!! 
   }
}

1 个答案:

答案 0 :(得分:2)

隐式范围使您可以控制拾取值的位置。

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