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 | abstract class Pet { // or, trait Pet, also works |
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 | class Dog extends Pet { |
And we could instantiate the Dog
class and see how these defined fields work.
1 | val dog = new Dog |
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 | class Cat extends Pet { |
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 | class Dog extends Pet { |
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:
- You want to create a base class that requires constructor arguments.
- 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 | abstract class Pet (name: String) { // trait doesn't allow this |
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 | trait Angry { |
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.