我正在尝试使用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) = ???
}
以下两个蛋糕模式模块(在此示例中都是依赖于HttpClient
功能的API):
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://...")
// ...
}
}
我们可以在每个模块中以不同的方式命名依赖项,为它们添加模块名称前缀,但这样会很麻烦且不优雅,如果我们自己没有完全控制模块,也无法工作。
有什么想法吗?我是否误解了蛋糕模式?
答案 0 :(得分:8)
您正确地获得了模式,并且您刚刚发现了它的重要限制。如果两个模块依赖于某个对象(例如HttpClient)并碰巧以相同的名称声明它(如httpClient),则游戏结束 - 您不会在一个Cake中单独配置它们。要么有两个蛋糕,就像丹尼尔建议或改变模块的来源,如果可以的话(正如Tomer Gabel暗示的那样)。
这些解决方案都存在问题。
有两个蛋糕(丹尼尔的建议)看起来很长,他们不需要一些共同的依赖。
重命名某些依赖项(如果可能)会强制您调整使用这些依赖项的所有代码。
因此,有些人(包括我)喜欢对这些问题免疫的解决方案,例如使用普通的旧构造函数并完全避免使用Cake。如果你测量它,它们不会给代码增加太多膨胀(Cake已经非常冗长)并且它们更加灵活。
答案 1 :(得分:3)
“你做错了”(TM)。您对Spring,Guice或任何IoC容器都有完全相同的问题:您将类型视为名称(或符号);你说“给我一个HTTP客户端”而不是“给我一个适合与fooApi通信的HTTP客户端”。
换句话说,您有多个名为httpClient
的HTTP客户端,它们不允许您区分不同的实例。这有点像使用@Autowired HttpClient而没有某种方法来限定引用(在Spring的情况下,通常是通过外部连线的bean ID)。
在蛋糕模式中,解决此问题的一种方法是使用其他名称限定该区别:FooApiModule
需要例如: def http10HttpClient: HttpClient
和BarApiModule
需要def connectionPooledHttpClient: HttpClient
。当“填写”不同的模块时,不同的名称都引用两个不同的实例,但也表示两个模块对其依赖关系的约束。
另一种选择(虽然在我看来并不干净)只是需要一个特定于模块的命名依赖项,即def fooHttpClient: HttpClient
,它只会强制显示外部连线,无论是谁混合你的模块。
答案 2 :(得分:2)
不是将FooApiModule
和BarApiModule
扩展到一个地方 - 这意味着它们共享依赖关系 - 而是将它们作为单独的对象,每个对象都相应地解决。
答案 3 :(得分:1)
似乎是已知的“机器人腿”问题。你需要构建一个机器人的两条腿,但是你需要为它们提供两个不同的脚。
如何使用蛋糕模式同时具有公共依赖关系并分开?
我们有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
}
答案 4 :(得分:0)
您的最终应用应如下所示:
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/)