Understanding Traits

A trait is a behavior that can be mixed into or assimilated into a class hierarchy. For example, to model a friend abstraction, we can mix a Friend trait into any class—Man, Woman, Dog, and so on—without having to inherit all those classes from a common base class.

To understand the benefits of traits, let’s design an example first without them. We’ll start with a class Human and make it friendly. In the simplest form, a friend is someone who listens. To support this abstraction, here is the listen method that we’d add to the Human class:

 
class​ Human(​val​ name: ​String​) {
 
def​ listen() = println(s​"Your friend $name is listening"​)
 
}
 
 
class​ Man(​override​ ​val​ name: ​String​) ​extends​ Human(name)
 
class​ Woman(​override​ ​val​ name: ​String​) ​extends​ Human(name)

One disadvantage in this code is the friendly quality does not quite stand out and is merged into the Human class. Furthermore, a few weeks into development, we realize we forgot man’s best friend. Dogs are great friends—they listen to us quietly when we have a lot to unload. But, it’s hard to make a Dog a friend in the current design. We can’t inherit a Dog from a Human for that purpose.

This is where Scala’s traits come in. A trait is like an interface with a partial implementation. The vals and vars we define and initialize in a trait get internally implemented in the classes that mix the trait in. Any vals and vars defined but not initialized are considered abstract, and the classes that mix in these traits are required to implement them. We can reimplement the Friend concept as a trait:

UsingTraits/Friend.scala
 
trait​ Friend {
 
val​ name: ​String
 
def​ listen() = println(s​"Your friend $name is listening"​)
 
}

Here we have defined Friend as a trait. It has a val named name that is treated as abstract. We also have the implementation of a listen method. The actual definition or the implementation of name will be provided by the class that mixes in this trait. Let’s look at ways to mix in the trait:

UsingTraits/Human.scala
 
class​ Human(​val​ name: ​String​) ​extends​ Friend
 
 
class​ Woman(​override​ ​val​ name: ​String​) ​extends​ Human(name)
 
class​ Man(​override​ ​val​ name: ​String​) ​extends​ Human(name)

The class Human mixes in the Friend trait. If a class does not extend from any other class, then use the extends keyword to mix in the trait. The class Human and its derived classes Man and Woman simply use the implementation of the listen method provided in the trait. We can override this implementation if we like, as you’ll see soon.

We can mix in any number of traits. To mix in additional traits, use the keyword with. We’ll also use the keyword with to mix in the first trait if a class already extends from another class like the Dog in this next example. In addition to mixing in the trait, we have overridden its listen method in Dog.

UsingTraits/Dog.scala
 
class​ Dog(​val​ name: ​String​) ​extends​ Animal ​with​ Friend {
 
//optionally override method here.
 
override​ ​def​ listen() = println(s​"$name's listening quietly"​)
 
}

The base class of Dog is Animal, defined separately here:

UsingTraits/Animal.scala
 
class​ Animal

We can call the methods of a trait on the instances of classes that mix it in, and also treat a reference to such classes as a reference of the trait:

UsingTraits/UseFriend.scala
 
object​ UseFriend ​extends​ App {
 
val​ john = ​new​ Man(​"John"​)
 
val​ sara = ​new​ Woman(​"Sara"​)
 
val​ comet = ​new​ Dog(​"Comet"​)
 
 
john.listen
 
sara.listen
 
comet.listen
 
 
val​ mansBestFriend : Friend = comet
 
mansBestFriend.listen
 
 
def​ helpAsFriend(friend: Friend) = friend.listen
 
 
helpAsFriend(sara)
 
helpAsFriend(comet)
 
}

Here’s the output from the previous code:

 
Your friend John is listening
 
Your friend Sara is listening
 
Comet's listening quietly
 
Comet's listening quietly
 
Your friend Sara is listening
 
Comet's listening quietly

Traits look similar to classes but have some significant differences. First, they require the mixed-in class to implement the uninitialized (abstract) variables and values declared in them. Second, their constructors cannot take any parameters. Traits are compiled into Java interfaces with corresponding implementation classes that hold any methods implemented in the traits.

Traits do not suffer from the method collision problem that generally arises from multiple inheritance. They avoid it by late binding with the method of the class that mixes them in. A call to super within a trait resolves to a method on another trait or the class that mixes it in, as you’ll soon see.

Mixing traits is not limited to classes. We can mix them into instances as well, as you’ll learn next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.169.40