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]}"
}
}