75

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.

How is that aspect of DI handled with the Cake pattern? The examples I've seen all involve mixing traits in statically.

mattinbits
  • 10,370
  • 1
  • 26
  • 35
Bill
  • 44,502
  • 24
  • 122
  • 213

5 Answers5

56

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.

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. Since ProductionDataSource inherits from DataSource, everything's great, and that application construction line compiles.

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. What you would do in that case is define a new trait TestDataSource which extends DataSource, and mix it in instead. 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.

Kunal
  • 85
  • 7
Dave Griffith
  • 20,435
  • 3
  • 55
  • 76
  • Pure canonical cake pattern, though I think the OP was looking for a way to add a trait to an already-instantiated object. The problem here is that it's only possible at the time of construction. – Kevin Wright Mar 02 '11 at 23:03
  • 10
    @Dave: are you really suggesting that your "if" statement is production ready and something that you would deploy to enterprise software? This is IMHO really poor code, because it fails to separate a deployment issue (which database) from a code issue (how an application is composed). Looking up the database is something that should be done in say a JNDI tree; it should never ever hard coded, because to make a change would require a re-deployment. – Ant Kutschera Jan 23 '12 at 17:35
  • 6
    I wouldn't do it that way in production code unless there were no alternative. This was simply the best answer I know of for the original question, which really did imply the need to change how the application is composed at run time. Simply saying "you never need to do that, since everything can be done by configuring components individually" begs the question. – Dave Griffith Jan 23 '12 at 21:18
  • 1
    If you're that concerned about the software engineering aspect of the cake pattern, the "test" clause could simply be embedded in your unit tests while the else/production clause would remain in the main application. – jhclark May 03 '12 at 12:36
  • 2
    @jhclark production, stage, development, test: how to handle these scenarios when you might be dealing with multiple databases depending on runtime conditions? The JNDI tree reference above is a good one, but I'm not sure if it covers all the bases, whereas a cake implementation can cover everything, just with a great deal more boilerplate, and as Dave mentions in his answer, "pain" ;-) – virtualeyes May 10 '12 at 09:07
  • In your example, I see that you're doing `val application = ...` - you have the capability to define your app with those dependencies. However, let's say that your main app is a Java class, with a Scala library that the Java code uses. How can the "production" class be used in this scenario? – Kevin Meredith Aug 14 '14 at 17:20
29

Scala is also a script language. So your configuration XML can be a Scala script. 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.

shellholic
  • 5,974
  • 1
  • 20
  • 30
5

The short answer is that Scala doesn't currently have any built-in support for dynamic mixins.

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.

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.

Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
  • are these new features explained somewhere? – pedrofurla Mar 03 '11 at 01:41
  • @pedrofurla - In the source code :) The 2.9 compiler has better ways of rolling back after a unit has failed in the typer phase, this was largely implemented for use by the presentation compiler (as used by eclipse and ensime). It's relevant to me because autoproxy-plugin uses a technique of typing in two passes, once to generate type information needed for delegate methods, and again for units that failed typing before the delegates had been synthesized. I was running into problems with inconsistencies being lest in the symbol table after the first failed pass. – Kevin Wright Mar 04 '11 at 11:44
  • 1
    @pedrofurla - Not that any of that is relevant for general Scala programming, only for people doing a certain class of trickery with compiler plugins. – Kevin Wright Mar 04 '11 at 11:44
3

Until the AutoProxy plugin becomes available, one way to achieve the effect is to use delegation:

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 vars inside a trait. Another downside is that if there are path dependent types within Module above, you won't be able to use delegation that easily.

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.

axel22
  • 32,045
  • 9
  • 125
  • 137
  • new DelegatedModule? you can declare "new" traits? What does the underscore mean in this particular context? I'm so confused. – Ryan Leach Jul 06 '15 at 11:01
0

Lift has something along those lines built in. It's mostly in scala code, but you have some runtime control. http://www.assembla.com/wiki/show/liftweb/Dependency_Injection

sblundy
  • 60,628
  • 22
  • 121
  • 123
  • Hmm, I generally like both Lift as a framework and Lift as a library. But in this exact case it's not a really good answer, as I see it. The main reason is that the program correctness isn't actually checked by the compiler. The library expects that any DI call may fail, even the result of each DI call is "Option"-al. – VasiliNovikov May 09 '14 at 12:31