There are multiple ways to model the abstraction you need in Scala. I will first describe the most simple pattern and analyze your problem, and then I will describe the most complex pattern, which is used in Scala collections.
The first thing to notice is that companion objects are the right place to put code you will need to call without having an instance of your class, while the place to factor out helpers which you use in instance methods are traits. Furthermore, the smart scala compiler will generate a single static method for your trait, and link all the classes which will use it to it.
From your code point of view, it is very easy to see how it can be factored out into a trait, and then by using the self-type notation, one can enforce that the trait FooTrait can be mixed only if the trait Bar is mixed as well.
class Foo extends FooTrait with Bar
trait FooTrait {
self:Bar =>
def foo: Quux = bar(this)
}
trait Bar {
def bar(f: Foo): Quux = frobnicate(f)
}
Please also note, if you do not want to expose the Bar interface through your Foo class, an alternative approach would be the following
class Foo extends FooTrait {
protected val barrer = Foo
}
trait FooTrait {
protected val barrer:Bar
def foo: Quux = barrer.bar(this)
}
trait Bar {
def bar(f: Foo): Quux = frobnicate(f)
}
object Foo extends Bar
This second approach works fine when you take a single class and a single companion objects, but does not scale well when you want to develop a hierarchy of classes where you now there is a companion object available for each of these classes, and you also want to enforce the companion object has certain characteristics with respect to the "companed class".
There is a more complex approach, which is used in Scala collections and which I warmly recommend you not to use unless strictly necessary.
Let's start from GenTraversable:
trait GenTraversable[+A]
extends GenTraversableLike[A, GenTraversable[A]]
with GenTraversableOnce[A]
with GenericTraversableTemplate[A, GenTraversable]
{
def seq: Traversable[A]
def companion: GenericCompanion[GenTraversable] = GenTraversable
}
object GenTraversable extends GenTraversableFactory[GenTraversable] {
implicit def canBuildFrom[A] = new GenericCanBuildFrom[A]
def newBuilder[A] = Traversable.newBuilder
}
As you see, the trait defines a companion object, which provides some basic infrastructure for building new collections of the same type (typically for filtering, mapping, and so on).
By going up in the hierarchy, you can see that the def companion
is refined:
trait GenIterable[+A]
extends GenIterableLike[A, GenIterable[A]]
with GenTraversable[A]
with GenericTraversableTemplate[A, GenIterable]
{
def seq: Iterable[A]
override def companion: GenericCompanion[GenIterable] = GenIterable
}
object GenIterable extends GenTraversableFactory[GenIterable] {
implicit def canBuildFrom[A] = new GenericCanBuildFrom[A]
def newBuilder[A] = Iterable.newBuilder
}
If you surf among the classes, you will understand that this mechanism is used to guarantee that for each concrete collection implementation, there is a companion in scope with some kind of properties with respect to the class itself. This is possible because it is legal to refine the return type of a method in a child class.
Nevertheless, in order to work correctly this mechanism requires some manual casts and a lot of generic parameters, as well as generic signatures.
trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNewBuilder[A, CC[A] @uncheckedVariance] {
protected[this] def newBuilder: Builder[A, CC[A]] = companion.newBuilder[A]
/** The generic builder that builds instances of $Coll
* at arbitrary element types.
*/
def genericBuilder[B]: Builder[B, CC[B]] = companion.newBuilder[B]
private def sequential: TraversableOnce[A] =this.asInstanceOf[GenTraversableOnce[A]].seq
// other code
}
foo
method. In a new trait? – Headland