简体   繁体   English

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

[英]How do you do dependency injection with the Cake pattern without hardcoding?

I just read and enjoyed the Cake pattern article . 我刚刚阅读并喜欢“蛋糕图案”文章 However, to my mind, one of the key reasons to use dependency injection is that you can vary the components being used by either an XML file or command-line arguments. 但是,在我看来,使用依赖项注入的主要原因之一是您可以更改XML文件或命令行参数所使用的组件。

How is that aspect of DI handled with the Cake pattern? 如何用Cake模式处理DI的这一方面? The examples I've seen all involve mixing traits in statically. 我所看到的示例都涉及静态混合特征。

Since mixing in traits is done statically in Scala, if you want to vary the traits mixed in to an object, create different objects based on some condition. 由于特征的混合是在Scala中静态完成的,因此,如果要更改混合到对象中的特征,请根据某些条件创建不同的对象。

Let's take a canonical cake pattern example. 让我们以一个标准的蛋糕模式为例。 Your modules are defined as traits, and your application is constructed as a simple Object with a bunch of functionality mixed in 您的模块被定义为特征,并且您的应用程序被构造为一个简单的对象,其中混合了许多功能

val application =
    new Object
extends Communications
   with Parsing
   with Persistence
   with Logging
   with ProductionDataSource
application.startup

Now all of those modules have nice self-type declarations which define their inter-module dependencies, so that line only compiles if your all inter-module dependencies exist, are unique, and well-typed. 现在,所有这些模块都具有漂亮的自类型声明,这些声明定义了它们的模块间依赖关系,因此只有在所有模块间依赖关系都存在,唯一且类型正确的情况下,行才可以编译。 In particular, the Persistence module has a self-type which says that anything implementing Persistence must also implement DataSource, an abstract module trait. 特别是,Persistence模块具有一个自类型,该类型表示实现Persistence的任何内容还必须实现DataSource,即抽象模块特征。 Since ProductionDataSource inherits from DataSource, everything's great, and that application construction line compiles. 由于ProductionDataSource继承自DataSource,所以一切都很好,并且该应用程序构造线得以编译。

But what if you want to use a different DataSource, pointing at some local database for testing purposes? 但是,如果您要使用其他数据源,并指向某个本地数据库以进行测试,该怎么办? Assume further that you can't just reuse ProductionDataSource with different configuration parameters, loaded from some properties file. 进一步假设您不能仅使用从某些属性文件加载的具有不同配置参数的ProductionDataSource。 What you would do in that case is define a new trait TestDataSource which extends DataSource, and mix it in instead. 在这种情况下,您将要做的是定义一个新特性TestDataSource,该特性扩展了DataSource并混入其中。 You could even do so dynamically based on a command line flag. 您甚至可以根据命令行标志动态地执行此操作。

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

Now that looks a bit more verbose than we would like, particularly if your application needs to vary its construction on multiple axes. 现在看起来比我们想要的更加冗长,特别是如果您的应用程序需要在多轴上更改其结构。 On the plus side, you usually you only have one chunk of conditional construction logic like that in an application (or at worst once per identifiable component lifecycle), so at least the pain is minimized and fenced off from the rest of your logic. 从好的方面来说,通常您只有一个条件构造逻辑块,如应用程序中那样(或者在每个可识别组件生命周期中最糟糕的情况是一次),因此至少可以将痛苦降到最低,并且与其余逻辑隔离开来。

Scala is also a script language. Scala也是一种脚本语言。 So your configuration XML can be a Scala script. 因此,您的配置XML可以是Scala脚本。 It is type-safe and not-a-different-language. 它是类型安全的,不是不同的语言。

Simply look at startup: 只需看一下启动:

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

is not so different than: 与以下内容没有什么不同:

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

You can always use DI, but you have one more tool. 您始终可以使用DI,但您还有另外一种工具。

The short answer is that Scala doesn't currently have any built-in support for dynamic mixins. 简短的答案是,Scala当前不对动态混入提供任何内置支持。

I am working on the autoproxy-plugin to support this, although it's currently on hold until the 2.9 release, when the compiler will have new features making it a much easier task. 我正在研究autoproxy-plugin以支持此功能,尽管该功能一直保持到2.9版本,此时编译器将具有新功能,这使它变得容易得多。

In the meantime, the best way to achieve almost exactly the same functionality is by implementing your dynamically added behavior as a wrapper class, then adding an implicit conversion back to the wrapped member. 同时,实现几乎完全相同的功能的最佳方法是将动态添加的行为实现为包装类,然后将隐式转换添加回包装的成员。

Until the AutoProxy plugin becomes available, one way to achieve the effect is to use delegation: 在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

But beware, the downside of this is that it's more verbose, and you have to be careful about the initialization order if you use var s inside a trait. 但是请注意,这样做的缺点是它比较冗长,如果在trait中使用var ,则必须注意初始化顺序。 Another downside is that if there are path dependent types within Module above, you won't be able to use delegation that easily. 另一个缺点是,如果上面的Module包含依赖于路径的类型,您将无法轻松地使用委托。

But if there is a large number of different implementations that can be varied, it will probably cost you less code than listing cases with all possible combinations. 但是,如果有很多不同的实现可以改变,那么与列出具有所有可能组合的案例相比,花费的代码可能更少。

Lift has something along those lines built in. It's mostly in scala code, but you have some runtime control. Lift有一些内置的东西。它主要在Scala代码中,但是您有一些运行时控件。 http://www.assembla.com/wiki/show/liftweb/Dependency_Injection http://www.assembla.com/wiki/show/liftweb/Dependency_Injection

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM