我刚刚阅读并喜欢“蛋糕图案”文章。但是,在我看来,使用依赖项注入的主要原因之一是您可以更改XML文件或命令行参数所使用的组件。
如何用Cake模式处理DI的这一方面?我所看到的示例都涉及静态混合特征。
Answers:
由于特征的混合是在Scala中静态完成的,因此,如果要更改混合到对象中的特征,请根据某些条件创建不同的对象。
让我们以一个标准的蛋糕模式为例。您的模块被定义为特征,并且您的应用程序被构造为一个简单的对象,其中混合了许多功能
val application =
new Object
extends Communications
with Parsing
with Persistence
with Logging
with ProductionDataSource
application.startup
现在,所有这些模块都有漂亮的自类型声明,这些声明定义了它们之间的模块依赖关系,因此只有在您所有的模块间依赖关系都存在,唯一且类型正确的情况下,才可以编译行。特别是,Persistence模块具有一个自类型,该类型表示实现Persistence的任何内容还必须实现DataSource,即抽象模块特征。由于ProductionDataSource继承自DataSource,所以一切都很好,并且该应用程序构造线得以编译。
但是,如果您要使用其他数据源,并指向某个本地数据库以进行测试,该怎么办?进一步假设您不能只使用从某些属性文件加载的具有不同配置参数的ProductionDataSource。在那种情况下,您将要做的是定义一个新特性TestDataSource,该特性扩展了DataSource并混入其中。您甚至可以根据命令行标志动态地执行此操作。
val application = if (test)
new Object
extends Communications
with Parsing
with Persistence
with Logging
with TestDataSource
else
new Object
extends Communications
with Parsing
with Persistence
with Logging
with ProductionDataSource
application.startup
现在看起来比我们想要的更加冗长,特别是如果您的应用程序需要在多轴上更改其结构。从好的方面来说,您通常只有一个条件构造逻辑块,例如在应用程序中(或在每个可识别组件生命周期中最糟糕的情况是一次),因此至少可以将痛苦降到最低,并且与其余逻辑隔离开来。
简短的答案是,Scala当前不对动态混入提供任何内置支持。
我正在研究autoproxy-plugin以支持此功能,尽管它直到2.9版本才被搁置,那时编译器将具有新功能,这使它变得容易得多。
同时,实现几乎完全相同的功能的最佳方法是将动态添加的行为实现为包装类,然后将隐式转换添加回包装的成员。
在AutoProxy插件可用之前,达到此效果的一种方法是使用委托:
trait Module {
def foo: Int
}
trait DelegatedModule extends Module {
var delegate: Module = _
def foo = delegate.foo
}
class Impl extends Module {
def foo = 1
}
// later
val composed: Module with ... with ... = new DelegatedModule with ... with ...
composed.delegate = choose() // choose is linear in the number of `Module` implementations
但是请注意,这样做的缺点是它比较冗长,如果var
在trait中使用s ,则必须注意初始化顺序。另一个缺点是,如果上面有依赖于路径的类型Module
,您将无法轻松地使用委托。
但是,如果有很多不同的实现可以改变,那么与列出具有所有可能组合的案例相比,花费的代码可能更少。
Lift有一些内置的东西。它主要在Scala代码中,但是您有一些运行时控件。http://www.assembla.com/wiki/show/liftweb/Dependency_Injection