Commit Logs

Scala Trait and Abstract Class

The concept of abstract class should be familiar to anyone with some background in Java or object-oriented programming (OOP) in general. It essentially 1) defines methods that enables inheriting subclass to use, 2) defines absract methods that requires implementation in subclass and 3) provides a common interface that allows interchanges among subclasses.

Scala borrows many things from Java, including abstract class, and provides some of its own, often times more powerful, variations, e.g. trait in this case. You will find out in this post the sameness/differences between trait and abstract class, and how to decide which one to use in your application.

Define properties

In Scala, abstract class and trait can both be used as interface to define properties for extending classes to implement. These defined properties can be either abstract or concrete depending on your specific problems. I attempt to cover some of the common concepts for both absract class and trait here, but I would only use abstract class in the following examples for simplicity.

A pet example

A great way to learn many OOP concepts is through toy examples and, since many people love pets (myself included), I will construct an abstract base class for pets as the example.

1
2
3
4
5
6
7
abstract class Pet { // or, trait Pet, also works
var age: Int = 0 // concrete
val hello: String // abstract
val greeting: String = s"I like to play with you!" // concrete
def sayHello: = { println(hello) }
override def toString = s"$hello, $greeting"
}

Abstract fields

For an abstract field, no matter whether it is val, var or def, it requires implementation in the extending classes. Therefore, we will need to provide a value for the abstract field hello when extend Pet in a subclass; otherwise, the compiler would complain something like error: class Dog needs to be abstract, since value hello in class Pet of type String is not defined. Specifically, we could extend Pet to construct a Dog class in the following way.

1
2
3
class Dog extends Pet {
val hello = "Woof"
}

And we could instantiate the Dog class and see how these defined fields work.

1
2
3
4
5
6
7
8
9
10
11
val dog = new Dog
// dog: Dog = Woof, I like to play with you!

dog.sayHello
// Woof

dog.age
// res0: Int = 0

println(dog)
// Woof, I like to play with you!

Note Scala compiler does not create a field in the resulting code for abstract fields; it generates the methods corresponding to those fields instead.

Concrete fields

For concrete fields that are already defined in the abstract class, we need to provide them with initial values in advance. Concrete val and var fields behave slightly differently when we would like to change their initial values when extend the abstract class.

For a concrete val field or def method, we need to override its value in the subclasses in order to change the initial value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Cat extends Pet {
val hello = "Meow"
override val greeting: String = s"I don't want to play with you!" // override initial value
override def sayHello: = { println( "SILENCE" ) } // override initial value
}

val cat = new Cat
// cat: Cat = Meow, I don't want to play with you!

cat.sayHello
// SILENCE

println(new Cat)
// Meow, I don't want to play with you!

For a concrete var field, we can change its value directly in the subclasses because of the property of var. We can even change its value for an instantiated object, since it has both getter and setter methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dog extends Pet {
val hello = "Woof"
age = 3
}

val dog = new Dog
// dog: Dog = Woof, I like to play with you!

dog.age
// res0: Int = 3

dog.age = 4
dog.age
// res1: Int = 4

Different use cases

The way to define fields and how they can be used are quite similar for both abstract class and trait. You might be wondering already - so, what are the differences between them, and, which one should I use for my next class inheritence?

As it turns out, these two concepts do have some different use cases. In general, trait is more flexible and encouraged in Scala, but you may have to use abstract class in certain circumstances.

abstract class

From Alvin Alexander, there are two main reasons to use abstract class in Scala:

  1. You want to create a base class that requires constructor arguments.
  2. The code will be called from Java code.

I will leave the second reason out for now, since it involves a bigger discussion that we will come back to in a separate post.

The first reason is pretty straightforward - trait doesn’t allow for constructor arguments. Therefore, if you want to do something like the next example, you will have to use abstract class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Pet (name: String) { // trait doesn't allow this
var age: Int = 0 // concrete
val hello: String // abstract
val greeting: String = s"I like to play with you!" // concrete
def sayHello: = { println(hello) }
override def toString = s"$hello, $greeting"
}

class Dog (name: String) extends Pet (name) {
val hello = "Woof"
override val greeting: String = s"I am $name and I like to play with you!"
}

val ben = new Dog("Ben")
// ben: Dog = Woof, I am Ben and I like to play with you!

trait

Besides the points above, trait in general is more powerful and can be used in many cases. A class can extend multiple trait, but only one abstract class. Moreover, trait can be combined together with abstract class to take advantage of its constructor parameter. These all make trait very flexible and more commonly used to implement base behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
trait Angry {
var isAngry: Boolean
}

trait BallLover {
def catchBall: = { println("catch the ball!") }
}

abstract class Pet (name: String) {
var age: Int = 0 // concrete
val hello: String // abstract
val greeting: String = s"I like to play with you!" // concrete
def sayHello: = { println(hello) }
override def toString = s"$hello, $greeting"
}

class Dog (name: String) extends Pet (name) with Angry with BallLover {
val hello = "Woof"
var isAngry = true
override val greeting: String = s"I am $name and I like to play with you!"
}

tom = new Dog("Tom")
// tom: Dog = Woof, I am Tom and I like to play with you!

tom.isAngry
// res0: Boolean = true

tom.catchBall
// catch the ball!

tom.isAngry = false
tom.isAngry
// res1: Boolean = false

You can also limit a trait so that it can only be added to classes that extend a certain superclass. For example, if you want BallLover to be a trait that can only be extended by subclasses of Pet, you just need to specify it so during its definition.

trait BallLover extends Pet {
  def catchBall: = { println("catch the ball!") }
}

In this case, any classes that are not extended from Pet wouldn’t be able to extend BallLover and would get an error: illegal inheritance instead.

Ending

Hopefull you have learnt some basics of the two most used building blocks in Scala OOP. You will almost certainly find abstract class and trait to be in the Scala application that you are going to building.

Just to recap, trait is more flexible and encouraged in Scala, but sometimes you will have to use abstract class to implement some base behaviors. The good news is that you can easily combine multiple trait together with an abstract class to get the best of both worlds.