如何在不进行硬编码的情况下使用Cake模式进行依赖项注入?


74

我刚刚阅读并喜欢“蛋糕图案”文章。但是,在我看来,使用依赖项注入的主要原因之一是您可以更改XML文件或命令行参数所使用的组件。

如何用Cake模式处理DI的这一方面?我所看到的示例都涉及静态混合特征。

Answers:


56

由于特征的混合是在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

现在看起来比我们想要的更加冗长,特别是如果您的应用程序需要在多轴上更改其结构。从好的方面来说,您通常只有一个条件构造逻辑块,例如在应用程序中(或在每个可识别组件生命周期中最糟糕的情况是一次),因此至少可以将痛苦降到最低,并且与其余逻辑隔离开来。


纯规范的蛋糕图案,尽管我认为OP一直在寻找一种向已经实例化的对象添加特征的方法。这里的问题是,只有在施工时才有可能。
凯文·赖特

10
@Dave:您是否真的建议您的“ if”语句已经准备好投入生产,并且您打算将其部署到企业软件中?恕我直言,这真是糟糕的代码,因为它无法将部署问题(哪个数据库)与代码问题(应用程序的组成方式)分开。查找数据库是应该用JNDI树来完成的。永远不要对其进行硬编码,因为要进行更改将需要重新部署。
Ant Kutschera 2012年

6
除非别无选择,否则我不会在生产代码中那样做。这只是我对原始问题所知的最佳答案,这确实暗示着需要在运行时更改应用程序的组成方式。简单地说“您永远不需要这样做,因为所有事情都可以通过单独配置组件来完成”,这引出了问题。
戴夫·格里菲斯

1
如果您担心蛋糕模式的软件工程方面,则“ test”子句可以简单地嵌入到单元测试中,而else / production子句将保留在主应用程序中。
jhclark

2
@jhclark生产,阶段,开发,测试:当您可能根据运行时条件处理多个数据库时,如何处理这些情况?上面的JNDI树参考是一个很好的参考,但是我不确定它是否涵盖了所有基础,而Cake实现可以覆盖所有内容,只是需要更多样板,而且正如Dave在他的回答中提到的那样,“痛苦” ;-)
virtualeyes 2012年

29

Scala也是一种脚本语言。因此,您的配置XML可以是Scala脚本。它是类型安全的,不是不同的语言。

只需看一下启动:

scala -cp first.jar:second.jar startupScript.scala

与以下内容没有什么不同:

java -cp first.jar:second.jar com.example.MyMainClass context.xml

您始终可以使用DI,但是您还有另外一种工具。


5

简短的答案是,Scala当前不对动态混入提供任何内置支持。

我正在研究autoproxy-plugin以支持此功能,尽管它直到2.9版本才被搁置,那时编译器将具有新功能,这使它变得容易得多。

同时,实现几乎完全相同的功能的最佳方法是将动态添加的行为实现为包装类,然后将隐式转换添加回包装的成员。


这些新功能在某处有解释吗?
pedrofurla 2011年

@pedrofurla-在源代码中:)2.9编译器具有更好的方法,可以在单位在typer阶段失败后进行回滚,这在很大程度上已实现供演示文稿编译器使用(如eclipse和ensime所使用)。这与我有关,因为autoproxy-plugin使用键入两次的技术,一次生成委托方法所需的类型信息,再一次生成在委托合成之前键入失败的单元。第一次失败通过后,我遇到了符号表中出现不一致的问题。
凯文·赖特

1
@pedrofurla-并不是所有这些都与通用Scala编程有关,仅适用于使用编译器插件进行某种欺骗的人。
凯文·赖特

3

在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,您将无法轻松地使用委托。

但是,如果有很多不同的实现可以改变,那么与列出具有所有可能组合的案例相比,花费的代码可能更少。


新的DelegatedModule?您可以宣布“新”特征?下划线在此特定情况下意味着什么?我很混乱。
Ryan The Leach 2015年

0

Lift有一些内置的东西。它主要在Scala代码中,但是您有一些运行时控件。http://www.assembla.com/wiki/show/liftweb/Dependency_Injection


嗯,我一般都喜欢将Lift作为框架,并将Lift作为库。但是在我看来,在这种确切情况下,这并不是一个很好的答案。主要原因是编译器实际上并未检查程序的正确性。该库期望任何DI调用都可能失败,即使每个DI调用的结果都是“ Option” -al。
VasiliNovikov 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.