Scala Cake 模式:如何避免依赖冲突?
Scala Cake Pattern: How to avoid Dependency Collisions?
我的问题与Scala Cake Pattern and Dependency Collisions非常相似。但我正在努力寻找 Daniel C 的回答中建议的具体解决方案。
问题来了:
一个ProductDetailsPage
(特质)需要两个独立的服务模块ProductServiceModule
和SessionModule
,分别由ProductServiceModuleWs
和SessionModuleWs
实现。
两个模块都依赖于 RestServiceConfigurationProvider
。
对于这个RestServiceConfigurationProvider
,只有一个具体的实现可用:DefaultRestServiceConfigurationProvider
(atm)。
另一方面,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 !!!!!
}
}
隐式作用域使您可以在一定程度上控制获取值的位置。
在某个地方,您将 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]}"
}
}
我的问题与Scala Cake Pattern and Dependency Collisions非常相似。但我正在努力寻找 Daniel C 的回答中建议的具体解决方案。
问题来了:
一个ProductDetailsPage
(特质)需要两个独立的服务模块ProductServiceModule
和SessionModule
,分别由ProductServiceModuleWs
和SessionModuleWs
实现。
两个模块都依赖于 RestServiceConfigurationProvider
。
对于这个RestServiceConfigurationProvider
,只有一个具体的实现可用:DefaultRestServiceConfigurationProvider
(atm)。
另一方面,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 !!!!!
}
}
隐式作用域使您可以在一定程度上控制获取值的位置。
在某个地方,您将 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]}"
}
}