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

Scala 如何在没有硬编码的情况下使用Cake模式进行依赖项注入?,scala,Scala,我只是阅读和享受。然而,在我看来,使用依赖注入的一个关键原因是,您可以改变XML文件或命令行参数所使用的组件 如何使用蛋糕模式处理DI的这一方面?我所看到的例子都涉及到静态地混合特性。Lift有一些内置的特性。它主要在scala代码中,但您有一些运行时控制 Scala也是一种脚本语言。因此,您的配置XML可以是Scala脚本。它是类型安全的,并且不是一种不同的语言 简单看一下启动: scala -cp first.jar:second.jar startupScript.scala 与以下内容

我只是阅读和享受。然而,在我看来,使用依赖注入的一个关键原因是,您可以改变XML文件或命令行参数所使用的组件


如何使用蛋糕模式处理DI的这一方面?我所看到的例子都涉及到静态地混合特性。

Lift有一些内置的特性。它主要在scala代码中,但您有一些运行时控制

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

简单看一下启动:

scala -cp first.jar:second.jar startupScript.scala
与以下内容没有太大区别:

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

您可以始终使用DI,但您还有一个工具。

因为在Scala中混合特征是静态完成的,如果您希望将混合的特征更改为对象,请根据某些条件创建不同的对象

让我们以一个规范的蛋糕模式为例。您的模块被定义为traits,您的应用程序被构造为一个简单的对象,其中混合了一系列功能

val application =
    new Object
extends Communications
   with Parsing
   with Persistence
   with Logging
   with ProductionDataSource
application.startup
现在,所有这些模块都有很好的自类型声明,这些声明定义了它们的模块间依赖关系,所以只有当所有模块间依赖关系都存在、唯一且类型良好时,行才会编译。特别是,持久性模块有一个自类型,表示任何实现持久性的东西都必须实现DataSource,这是一个抽象模块特性。由于ProductionDataSource继承自DataSource,所以一切都很好,应用程序构造行可以编译

但是,如果您想使用不同的数据源,指向某个本地数据库进行测试,该怎么办?进一步假设您不能仅使用从某些属性文件加载的不同配置参数重用ProductionDataSource。在这种情况下,您要做的是定义一个新的trait 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插件来支持这一点,尽管它目前被搁置到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
但是要注意,这样做的缺点是它更加冗长,如果在trait中使用
var
s,那么必须小心初始化顺序。另一个缺点是,如果上面的
模块
中存在路径依赖类型,您将无法轻松地使用委派


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

纯粹的规范蛋糕模式,尽管我认为OP正在寻找一种向已经实例化的对象添加特征的方法。这里的问题是,只有在构建时才有可能实现。@Dave:你真的是在暗示你的“如果”语句是生产就绪的,并且是要部署到企业软件中的吗?这是非常糟糕的代码,因为它无法将部署问题(哪个数据库)与代码问题(应用程序的组成方式)分开。查找数据库应该在JNDI树中完成;它永远不应该硬编码,因为进行更改需要重新部署。除非别无选择,否则我不会在生产代码中这样做。这是我所知道的关于原始问题的最佳答案,这确实意味着需要更改应用程序在运行时的组成方式。简单地说“你永远不需要这样做,因为一切都可以通过单独配置组件来完成”就回避了这个问题。如果你那么关心蛋糕模式的软件工程方面,“测试”子句可以简单地嵌入到单元测试中,而else/production子句将保留在主应用程序中。@jhlark生产、阶段、开发、测试:当您可能根据运行时条件处理多个数据库时,如何处理这些场景?上面的JNDI树参考是一个很好的参考,但我不确定它是否涵盖了所有的基础,而cake实现可以涵盖一切,只需要更多的样板文件,正如Dave在回答中提到的,“痛苦”;-)这些新特性在什么地方解释过了吗?@pedrofurla-在源代码中:)2.9编译器有更好的方法在单元在打字机阶段失败后回滚,这主要是为表示编译器(eclipse和ensime使用的)实现的。这与我有关,因为autoproxy插件使用了一种分两次键入的技术,一次生成委托方法所需的类型信息,另一次生成委托合成之前键入失败的单元所需的类型信息。在第一次失败通过后,我遇到了符号表中不一致的问题。@pedrofurla-这与一般的Scala编程无关,只适用于使用编译器插件进行某种欺骗的人。嗯,我通常既喜欢作为框架的Lift,也喜欢作为库的Lift