问题 斯卡拉蛋糕模式和依赖性冲突
我正在尝试使用Cake Pattern在Scala中实现依赖注入,但是遇到依赖冲突。由于我找不到具有这种依赖关系的详细示例,这是我的问题:
假设我们有以下特征(有2个实现):
trait HttpClient {
def get(url: String)
}
class DefaultHttpClient1 extends HttpClient {
def get(url: String) = ???
}
class DefaultHttpClient2 extends HttpClient {
def get(url: String) = ???
}
以下两个蛋糕模式模块(在这个例子中都是依赖于我们的API的API) HttpClient
为了他们的功能):
trait FooApiModule {
def httpClient: HttpClient // dependency
lazy val fooApi = new FooApi() // providing the module's service
class FooApi {
def foo(url: String): String = {
val res = httpClient.get(url)
// ... something foo specific
???
}
}
}
和
trait BarApiModule {
def httpClient: HttpClient // dependency
lazy val barApi = new BarApi() // providing the module's service
class BarApi {
def bar(url: String): String = {
val res = httpClient.get(url)
// ... something bar specific
???
}
}
}
现在,当创建使用两个模块的最终应用程序时,我们需要提供 httpClient
两个模块的依赖关系。但是,如果我们想为每个模块提供不同的实现呢?或者只是简单地提供不同配置的不同实例(比如说不同 ExecutionContext
例如)?
object MyApp extends FooApiModule with BarApiModule {
// the same dependency supplied to both modules
val httpClient = new DefaultHttpClient1()
def run() = {
val r1 = fooApi.foo("http://...")
val r2 = barApi.bar("http://...")
// ...
}
}
我们可以在每个模块中以不同的方式命名依赖项,在它们前面加上模块名称,但这样做会很麻烦且不够优雅,如果我们自己没有完全控制模块,也无法工作。
有任何想法吗?我是否误解了蛋糕模式?
1052
2017-12-08 22:18
起源
答案:
你正确地得到了模式,你刚刚发现了它的重要限制。如果两个模块依赖于某个对象(例如HttpClient)并碰巧以相同的名称声明它(如httpClient),则游戏结束 - 您不会在一个Cake中单独配置它们。要么有两个蛋糕,就像丹尼尔建议或改变模块的来源,如果可以的话(正如Tomer Gabel暗示的那样)。
每种解决方案都有其问题。
有两个蛋糕(丹尼尔的建议)看起来很长,他们不需要一些共同的依赖。
重命名一些依赖项(如果可能)会强制您调整使用这些依赖项的所有代码。
因此,有些人(包括我)更喜欢免疫这些问题的解决方案,例如使用普通的旧构造函数并完全避免使用Cake。如果你测量它,它们不会给代码增加太多膨胀(Cake已经非常冗长)并且它们更加灵活。
8
2017-12-09 23:29
“你做错了”(TM)。您对Spring,Guice或任何IoC容器都有完全相同的问题:您将类型视为名称(或符号);你说“给我一个HTTP客户端”而不是“给我一个适合与fooApi通信的HTTP客户端”。
换句话说,您有多个HTTP客户端都已命名 httpClient
,它不允许您对不同的实例进行任何区分。这有点像使用@Autowired HttpClient而没有某种方法来限定引用(在Spring的情况下,通常是通过外部连线的bean ID)。
在蛋糕模式中,解决此问题的一种方法是使用不同的名称限定该区别: FooApiModule
要求例如一个 def http10HttpClient: HttpClient
和 BarApiModule
要求 def connectionPooledHttpClient: HttpClient
。当“填写”不同的模块时,不同的名称都引用两个不同的实例,但也表示两个模块对其依赖性的约束。
另一种选择(虽然在我看来并不干净)是简单地要求特定于模块的命名依赖,即 def fooHttpClient: HttpClient
,这简单地强制显示外部布线,无论是谁混合您的模块。
3
2017-12-09 15:01
而不是延伸 FooApiModule
和 BarApiModule
在一个地方 - 这意味着他们共享依赖关系 - 使它们成为单独的对象,每个对象都相应地解决它们的依赖关系。
2
2017-12-08 22:56
答案:
你正确地得到了模式,你刚刚发现了它的重要限制。如果两个模块依赖于某个对象(例如HttpClient)并碰巧以相同的名称声明它(如httpClient),则游戏结束 - 您不会在一个Cake中单独配置它们。要么有两个蛋糕,就像丹尼尔建议或改变模块的来源,如果可以的话(正如Tomer Gabel暗示的那样)。
每种解决方案都有其问题。
有两个蛋糕(丹尼尔的建议)看起来很长,他们不需要一些共同的依赖。
重命名一些依赖项(如果可能)会强制您调整使用这些依赖项的所有代码。
因此,有些人(包括我)更喜欢免疫这些问题的解决方案,例如使用普通的旧构造函数并完全避免使用Cake。如果你测量它,它们不会给代码增加太多膨胀(Cake已经非常冗长)并且它们更加灵活。
8
2017-12-09 23:29
“你做错了”(TM)。您对Spring,Guice或任何IoC容器都有完全相同的问题:您将类型视为名称(或符号);你说“给我一个HTTP客户端”而不是“给我一个适合与fooApi通信的HTTP客户端”。
换句话说,您有多个HTTP客户端都已命名 httpClient
,它不允许您对不同的实例进行任何区分。这有点像使用@Autowired HttpClient而没有某种方法来限定引用(在Spring的情况下,通常是通过外部连线的bean ID)。
在蛋糕模式中,解决此问题的一种方法是使用不同的名称限定该区别: FooApiModule
要求例如一个 def http10HttpClient: HttpClient
和 BarApiModule
要求 def connectionPooledHttpClient: HttpClient
。当“填写”不同的模块时,不同的名称都引用两个不同的实例,但也表示两个模块对其依赖性的约束。
另一种选择(虽然在我看来并不干净)是简单地要求特定于模块的命名依赖,即 def fooHttpClient: HttpClient
,这简单地强制显示外部布线,无论是谁混合您的模块。
3
2017-12-09 15:01
而不是延伸 FooApiModule
和 BarApiModule
在一个地方 - 这意味着他们共享依赖关系 - 使它们成为单独的对象,每个对象都相应地解决它们的依赖关系。
2
2017-12-08 22:56
似乎是已知的“机器人腿”问题。你需要构建一个机器人的两条腿,但是你需要为它们提供两个不同的脚。
如何使用蛋糕模式具有共同的依赖关系并分开?
让我们 L1 <- A, B1
; L2 <- A, B2
。你想拥有 Main <- L1, L2, A
。
要拥有单独的依赖项,我们需要两个较小的蛋糕实例,并使用常见的依赖项进行参数化。
trait LegCommon { def a:A}
trait Bdep { def b:B }
class L(val common:LegCommon) extends Bdep {
import common._
// declarations of Leg. Have both A and B.
}
trait B1module extends Bdep {
val b = new B1
}
trait B2module extends Bdep {
def b = new B2
}
在 Main
我们将在蛋糕和两条腿上有共同点:
trait Main extends LegCommon {
val l1 = new L(this) with B1module
val l2 = new L(this) with B2module
val a = new A
}
1
2017-12-10 06:29
您的最终应用应如下所示:
object MyApp {
val fooApi = new FooApiModule {
val httpClient = new DefaultHttpClient1()
}.fooApi
val barApi = new BarApiModule {
val httpClient = new DefaultHttpClient2()
}.barApi
...
def run() = {
val r1 = fooApi.foo("http://...")
val r2 = barApi.bar("http://...")
// ...
}
}
这应该工作。 (改编自这篇博文: http://www.cakesolutions.net/teamblogs/2011/12/19/cake-pattern-in-depth/)
0
2017-12-11 06:59