We’ve been using Scala for quite awhile at work, and I think one of the tricky things to figure out is when to use/not use a trait.
Personally, I have two rules of thumb that I apply:
- Is the trait purposefully adding useful methods to the class’s public API?
- Does the trait’s implementation use instance data from the class?
Is so, use the trait. If not, you’re likely, IMO, abusing traits as a fancy way to organize code that is not coherently tied to the conceptual type hierarchy.
The core principle behind both of these rules is that I personally prefer object instances to be as small and specific as possible. Tiny, minimal public APIs. Tiny, minimal responsibilities. E.g. the Single Responsibility Principle.
Without following these rules of thumb, I think it’s easy to slide down the slippery slope of abusing traits merely as glorified imports.
Example 1
E.g.:
trait SomethingUseful {
def aUtilMethod() = { ... }
}
class MyClass extends SomethingUseful {
def foo() = {
... = aUtilMethod()
}
}
In this example, aUtilMethod
is neither a method that we really want exposed to MyClass
’s clients, nor is it using any instance state from MyClass
. So, it might as well be a static method call instead:
object SomethingUseful {
def aUtilMethod() = { ... }
}
class MyClass {
def foo() = {
... = Object.aUtilMethod()
}
}
Example 1b
A slight variation on the above is where SomethingUseful
has state:
E.g.:
trait SomethingUseful {
private var internalState = ...
def aUtilMethod() = {
// access internalState
}
}
class MyClass extends SomethingUseful {
def foo() = {
... = aUtilMethod()
}
}
So, we cannot just change aUtilMethod
to a static call.
However, SomethingUseful
still does not access any MyClass
state. And there is really no reason for us to say “MyClass
is a SomethingUseful
”, so we should prefer composition:
class MyClass {
private val useful = new SomethingUseful()
def foo() = {
... = useful.aUtilMethod()
}
}
I wonder if perhaps the fascination with traits is due to composition being more verbose; instead of with SomethingUseful
to mix it in, we need a field, which gets it’s own dedicated line of code, and then later our aUtilMethod()
needs a useful.
prefix instead of a naked aUtilMethod()
.
Example 2
The previous example was pretty straight forward; more common is something that looks more innocent:
trait SomethingUseful {
def aUtilMethod() = { ... }
def aDependency(): Dependency
}
class MyClass extends SomethingUseful {
def foo() = {
... = aUtilMethod()
}
override def aDependency() = ...
}
In this example, aUtilMethod
needs a Dependency
instance. And MyClass
wants to provide that.
However, MyClass
is still not really gaining any useful public API from SomethingUseful
, and if anything usually dirtying it’s public API because aDependency
and aUtilMethod
are rarely defined as protected
.
Depending on the number/type of dependencies, I think a static solution may again be appropriate:
object SomethingUseful {
def aUtilMethod(dep: Dependency) = { ... }
}
class MyClass {
def foo() = {
... = Object.aUtilMethod(dep)
}
}
Or, if dependencies are getting fun, make SomethingUseful
into its own component:
class SomethingUseful(dep: Dependency) {
def aUtilMethod() = { ... }
}
class MyClass(useful: SomethingUseful) {
def foo() = {
... = useful.aUtilMethod()
}
}
And then use your dependency management approach of choice.
(Disclaimer: I had this in my draft folder for awhile, and think it’s basically good, but slightly off the top of my head. I think the topic could use a bit more flushing out/rigorous thought than I’ve put in to it so far.)