1

In Scala dependency injection with type annotation, the injected class/object reference can be either implemented as a def trait member or val abstract member, like:

trait InjectedTrait {}

class InjectedClass extends InjectedTrait {}

trait TestTrait {
    def injectedTrait: InjectedTrait
}

class TestClass {
    this: TestTrait =>
}

// In main()
val obj = new TestClass() with TestTrait {
        val injectedTrait = new InjectedClass()
}

or

abstract class AbstractInjectedClass {}

class InjectedClass extends AbstractInjectedClass {}

trait TestTrait {
    val injectedClass: AbstractInjectedClass
}

class TestClass {
    this: TestTrait =>
}

// In main()
val obj = new TestClass() with TestTrait {
    override val injectedClass = new InjectedClass()
}

Any reasons you would prefer one over the other? - I personally like the second one because the 'override' keyword clearly expresses what's happening.

2
  • What if you want to extend more then one class? I mean multiple inheritance like class a extends b then what? That’s one of the reason we have traits. Commented May 31, 2018 at 17:44
  • Well that's true. But in DI I don't think I'd want to have many mixins for the class objects injected. That just makes things messy rather than having a clearer inheritance hierarchy. I would rather build another class object to inject. Commented May 31, 2018 at 18:53

1 Answer 1

0

You are mixing up some concepts that are relatively orthogonal to each other even though they all allow you some form interaction with Scala's OO model, namely:

  • traits and abstract classes
  • abstract def and vals
  • the override qualifier

You may notice that the compiler does not forbid you, for your particular example, to interchangeably use a trait or an abstract class, as well as adding and removing the override qualifier whether the concrete class implements a trait or an abstract class. The only thing you cannot do is implementing a def out of a non-concrete base class which defines the member as a def (more on that later).

Let's go through these concepts one at a time.

traits and abstract classes

One of the main differences between traits and abstract classes is that the former can be used for multiple inheritance. You can inherit from one or more traits, an abstract class and one or more traits or one abstract class.

The other is that abstract classes can have constructor parameters, which makes them more interesting to handle dynamic construction of objects with parameters that are only available at runtime.

You should reason about these characteristics when deciding which one to use them. In the DI use case, since you may want to establish a self type annotation that refers to more than one type (e.g.: self => Trait1 with Trait2), traits tend to give you more freedom and are generally favored.

It may be worth noting at this point that historically abstract classes tended to interact better with the JVM (as they had an analogous construct Java) and that in Scala 3 (currently under development under the name Dotty) traits will have the possibility to get constructor parameters, making them more powerful then abstract classes.

abstract defs and vals

It is always suggested that you define abstract members as defs, as this leaves you the freedom to define it as either a def or a val in concrete implementations.

The following is legal:

trait Trait {
  def member: String
}

class Class extends Trait {
  val member = "Class"
}

The following doesn't compile:

trait Trait {
  val member: String
}

class Class extends Trait {
  def member = "Class"
}

the override qualifier

Using override is highly suggested precisely because of the reason you mentioned, but please note that this is not bound to using val. In fact, the following is legal code:

trait Trait {
  val member: String
}

class Class extends Trait {
  val member = "Class"
}

The override qualifier is mandatory only if the extended class or trait already provides a concrete implementation of the particular field or method you are overriding. The following code does not compile and the compiler will tell you that the override keyword is mandatory:

trait Trait {
  def member = "Trait"
}

class Class extends Trait {
  val member = "Class"
}

But as I already mentioned and as you already noted, the override keyword is very useful to clarify what is being overridden and what is not, so it is highly suggested that you use it even when it's not strictly necessary.


Bonus of vals and defs

Another good reason to not use vals in traits is to prevent making the order in which their are inherited meaningful, that in real-life scenarios can lead to some tough head-scratcher when traits also provide concrete implementations.

Consider the following code (that you can also run and play with here on Scastie):

trait HasTrunk {
  val trunk = {
    println("I have a trunk")
    "trunk"
  }
}

trait HasLegs {
  val legs = {
    println("I have legs")
    "legs"
  }
}

final class Elefant extends HasTrunk with HasLegs
final class Fly extends HasLegs with HasTrunk

new Elefant
new Fly

Since vals must be evaluated at construction time, their side-effects are too: notice how the construction behavior changes, with operations performed at different times, even though the two classes extend the same traits.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your very thorough explanation. However, I'm a bit lost on the point you're trying to make. For me, trait is more or less a Java interface that can hold member variables and methods, and this is useful when you want Mixin. Obviously, it can be used for multi-inheritance but - Java removed support for this coz it's messy. Of course traits can give the injected class multiple mixins, but in that case, I prefer to inject other classes with those mixins. I feel the design would be clearer coz the class hierarchy is simpler.
My point was: 1. extract the orthogonal features you used to discuss them one by one (as the two examples are not the two possible ways of mixing those) and 2. make sure to give you a deeper understanding of those individual features to give you an idea of how to apply those in your context. My personal suggestion, would be to to go for traits with defs only and inject capabilities with simple object composition through constructor parameters. Mix-ins tend to inherently complicate the hierarchy, even if you try to keep them simple. But the answers wanted to not be as opinionated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.