CHAPTER 7

image

Traits

In this chapter you will learn how to construct reusable parts of a program and deal with the tribulations of multiple inheritance, sidestepping the disadvantages of single inheritance by means of the mixing of compositions made possible by traits.

A trait provides code reusability in Scala by encapsulating method and state and then offering the possibility of mixing them into classes thus allowing code reuse. In this way a class can be mixed in with a myriad of traits unlike inheritance where each class is allowed to inherit from just one superclass. Moreover, other than using the keyword trait a trait definition resembles a class definition as illustrated in Listing 7-1.

Listing 7-1. The Definition of Trait Gliding

trait Gliding {
def gliding() {
println("gliding")
}
}

This trait is named Gliding. It does not declare a superclass, so like a class, it has the default superclass of AnyRef. It defines one concrete method, named gliding.

scala> trait Gliding {
     | def gliding() {
     | println("gliding")
     | }
     | }
defined trait Gliding

The Gliding trait is a simple example but adequate to show how traits works. Now we will explore how mixin composition is made possible by trait.

Using Traits as Mixins

With single inheritance a class can inherit methods and fields from only one class. Multiple inheritance enables the class to inherit methods and fields from more than one class; however, multiple inheritance can be problematic as the order of inheriting classes may affect the behavior of the subclass inadvertently. The mixin composition is a better approach toward solving the problems of multiple inheritance, sidestepping the drawbacks of single inheritance. In Java, a class can implement any arbitrary number of interfaces toward multiple abstractions. Unlike Java, Scala provides a mechanism for defining and using reusable code in interfaces, which will be valid for all the classes implementing the interface. You have abstract classes for defining and using such reusable code but a class can extend only one abstract class eliminating the possibility of multiple inheritance. The term mixin is used for such reusable code that could be independently maintained. You can use traits in a way similar to the way in which Java interfaces are used. When you add implementation to traits they become Mixins. You can create a trait that inherits from a Class, as well as a Class that extends a trait. Once a trait is defined, it can be mixed in to a class using either the extends or with keywords. Listing 7-2 shows a class that mixes in the Gliding trait using extends.

Listing 7-2. Mixin by Extending the Trait

class Glider extends Gliding {
override def toString = "glider"
}
scala> class Glider extends Gliding {
     | override def toString = "glider"
     | }
defined class Glider

You can use the extends keyword to mix in a trait; in that case you implicitly inherit the trait’s superclass. In Listing 7-2, class Glider mixes in Gliding. You can use the methods inherited from a trait as following:

scala> val glider = new Glider
glider: Glider = glider
scala> glider.gliding()
gliding

A trait also defines a type. Here’s an example in which Gliding is used as a type as illustrated in the following example:

scala> val g: Glider = glider
g: Glider = glider
scala> g.gliding()
gliding

In the example shown earlier the type of g is a Glider trait, and so g could be initialized with any object whose class mixes in Glider.

In the Listing 7-3, class Glider has inherited an implementation of gliding from trait Glider. The class Glider could override gliding as illustrated in Listing 7-3.

Listing 7-3. Overriding the Trait’s Method

class Glider extends Gliding {
override def toString = "glider"
override def gliding() {
println("race for now "+ toString )
}
}

Because Glider overrides Gliding’s implementation of gliding, you’ll get a new behavior when you call it:

scala> class Glider extends Gliding {
     | override def toString = "glider"
     | override def gliding() {
     | println("race for now "+ toString )
     | }
     | }
defined class Glider
scala> val glider = new Glider
glider: Glider = glider
scala> glider.gliding()
race for now glider

Fundamentally, traits are akin to Java interfaces. As with interfaces, you could just declare the methods in your trait that you want your extending classes to implement as illustrated in Listing 7-4.

Listing 7-4. Declaring Methods in a Trait

trait TraitA {
def methodA
def methodAWithParam(param :String)
def methodWithReturnType: String
}

Listing 7-4 shows a trait that declares the methods that don’t take any argument. The methods without argument can be declared with a def keyword followed by the method name as illustrated in the first method def methodA in Listing 7-4. If a method requires parameter, you could list them as usual as illustrated in Listing 7-4.

One trait can extend another trait as illustrated in Listing 7-5. TraitA can extend TraitB.

Listing 7-5. Trait Extending Another Trait

trait TraitB extends TraitA{
def methodB
}

When a class extends a trait, it uses the extends and with keywords based on whether the class extends one trait or several traits. When a class extends one trait, use the extends keyword as illustrated in Listing 7-6.

Listing 7-6. Extending One Trait

class ClassA extends TraitA{
// code
}

If a class extends more than one trait, use extends for the first trait and with to mix in the other traits as illustrated in Listing 7-7.

Listing 7-7. Extending Multiple Traits

class ClassA extends TraitA with TraitB{
// code
}

If a class extends a class and a trait, always use extends before the class name, and use with before the trait’s name as illustrated in Listing 7-8.

Listing 7-8. Extending Class and Traits

class ClassA extends ClassB withTraitA with TraitB{
// code
}

A class extending the trait must implement all the abstract methods of trait, unless the class extending a trait is itself abstract, as illustrated in Listing 7-9.

Listing 7-9. Concrete Class Must Implement All Abstract Methods of Trait

class ClassA extends TraitA {
def methodA { // code... }
def methodAWithParam(param :String){ // code... }
def methodWithReturnType: String{ // code... }
}

In Listing 7-9, ClassA is not declared abstract and therefore implements all abstract methods of trait TraitA.

Image Note  A trait can be comprised of both abstract and concrete methods.

However, if a class extends a trait but does not implement the abstract methods defined in the trait, the class extending the trait must be declared abstract as illustrated in Listing 7-10.

Listing 7-10. Extending Class Not Implementing Abstract Methods of Trait Must Be Abstract

abstract class ClassA extends TraitA {
def methodA { // code... }
def methodAWithParam(param :String){ // code... }
}

In Listing 7-10, ClassA does not implement the methodWithReturnType method of TraitA shown in Listing 7-4.

The subclass of a trait can choose, if it prefers, to override the trait’s method.

Listing 7-11 illustrates a Vehicle trait that provides an implementation for the drive method.

Listing 7-11. Trait with Implementation

trait Vehice {
def drive { println("Driving") }
def race
}

In Listing 7-12, drive is a concrete method and race is an abstract method.

The Car class in Listing 7-12 does not override the drive method of Vehicle trait.

Listing 7-12. Subclass Does Not Override the Trait’s DriveMethod

class Car extends Vehicle {
def race { ("Racing the car") }
}

The Boat class in Listing 7-13 overrides the drive method of Vehicle trait.

Listing 7-13. Subclass Overridesthe Trait’s DriveMethod

class Boat extends Vehicle {
override def drive { ("float") }
def race { ("Racing boat.") }
}

Image Note  Although Scala has abstract classes, it’s recommended to use traits instead of abstract classes to implement base behavior because a class can extend only one abstract class, but it can implement multiple traits. If you want the base behavior to be inherited in Java code, use an abstract class.

You can also use fields in your traits. The fields of a trait can be declared as either var or val and can be concrete by defining the field with an initial value or the field can be made abstract by not assigning the initial value. Listing 7-14 illustrates a trait CarTrait with abstract field door and concrete field seat.

Listing 7-14. Trait with Abstract and Concrete Fields

trait CarTrait {
var door: Int
var seat = 4
}

Listing 7-15 illustrates Car class that extends the CarTrait. As you can see you don’t need to use the override keyword to override var fields door and seat.

Listing 7-15. Override Keyword Not Necessary for var Field

class Car extends CarTrait {
var door = 4
seat = 5
}

You need to use the override keyword in a subclass of a trait to override a val field as illustrated in Listing 7-16.

Listing 7-16. Override Keyword Necessary for val Field

trait CarTrait {
val door: Int
}
class Car  extends CarTrait {
override val door = 5
}

In the class Car that extends the CarTrait trait, you’ll need to define the values for the abstract fields, otherwise you need to define the class as abstract.

As you can see traits can declare fields and maintain state. The syntax of class definition and trait definition is exactly the same except that a class definition can have the parameters passed to the primary constructor of a class but a trait definition cannot have such parameters.

Listing 7-17. Trait Definition Cannot Have Parameters

class Car(door: Int )trait Car (door: Int) // does not compile

In the next section we will explore using traits for modelling complex class hierarchies that you cannot model in Java.

Traits and Class Hierarchies

One of the big challenges with developing a class hierarchy when you are constrained by single inheritance is figuring out what things should be base classes and where things should go in the class hierarchy. If we’re modeling living things, how do you model things with legs when that can include any animal? Should there be LeggedAnimals and LeglessAnimals? But then, how do you deal with Mammals and Reptiles? Maybe we can make HasLegs an interface, but then I can give a Plant legs. Scala to the rescue.

We’ve already seen that traits can implement methods. Additionally, traits can have rules about what kind of classes and other traits they can be mixed into. Further, you can declare method parameters that are a consolidation of types, for example:

def foo(bar: Baz with Blarg with FruitBat)

Only instances of classes that extend Baz, Blarg, and FruitBat may be passed into this method.

Let’s model some living things.

Listing 7-18. Modelling Living Things

abstract class LivingThing
abstract class Plant extends LivingThing
abstract class Fungus extends LivingThing
abstract class Animal extends LivingThing

Good so far. A LivingThing must be a plant, fungus, or animal. But, what about legs? Who can have legs?

Listing 7-19. Trait HasLegs

trait HasLegs extends Animal {
  def walk() {println("Walking")}
}

The HasLegs trait extends Animal. But Animal is a class, so what does it mean for a trait to extend a class? It means that the compiler will only let you mix HasLegs into something that subclasses from Animal. Thus, we’ve defined that only animals have legs, but any type of animal can have legs. It’s the same for HasWings as shown in Listing 7-20.

Listing 7-20. Trait HasWings

trait HasWings extends Animal {
  def flap() {println("Flap Flap")}
}

But, only things with wings can fly. This is a different notation. We define the rules of the self type with this: HasWings =>. The compiler flags an error if this trait is not mixed into a class that also extends HasWings. So, we can use self types to define the rules for what classes a given trait can be mixed into (see Listing 2-11).1

Listing 7-21. Trait Flies

trait Flies {
  this: HasWings =>
  def fly() {println("I'm flying")}
}

And Birds have wings and legs:

abstract class Bird extends Animal with HasWings with HasLegs

Let’s define a couple of different Birds in Listing 7-22.

Listing 7-22. Concrete Birds

class Robin extends Bird with Flies
class Ostrich extends Bird

All mammals have a bodyTemperature as shown in Listing 7-23.

Listing 7-23. Mammal Behavior

abstract class Mammal extends Animal {
  def bodyTemperature: Double
}

Some animals know their name, and if they do, they respond to their name (see Listing 7-24).

Listing 7-24. KnowsName Trait

trait KnowsName extends Animal {
  def name: String
}

So, in Listing 7-25 a Dog is a Mammal that has legs and knows (responds to) its name.

Listing 7-25. Dog Has Legs and Knows Its Name

class Dog(val name: String) extends Mammal with HasLegs with KnowsName {
  def bodyTemperature: Double = 99.3
}

Some cats and children come to mind as mammals that know their own name but will sometimes ignore their name (Listing 7-26).

Listing 7-26. IgnoresNames Trait

trait IgnoresName {
  this: KnowsName =>
  def ignoreName(when: String): Boolean

  def currentName(when: String): Option[String] =
    if (ignoreName(when)) None else Some(name)
}

Now we can define a Cat class that has legs, knows its name, and ignores its name except at dinner time (Listing 7-27).

Listing 7-27. Cat Ignores Name Except at Dinner Time

class Cat(val name: String) extends Mammal with HasLegs with
  KnowsName with IgnoresName {
  def ignoreName(when: String) = when match {
     case "Dinner" => false
     case _ => true
  }
  def bodyTemperature: Double = 99.5
}

Some Animals can be Athletes, and Runners are Athletes with legs:

trait Athlete extends Animal.

Listing 7-28. Runner Trait

trait Runner {
  this: Athlete with HasLegs =>
  def run() {println("I'm running")}
}

A Person is a Mammal with legs and knows its name (Listing 7-29).

Listing 7-29. Person Is Mammal with Legs and Knows Its Name

class Person(val name: String) extends Mammal with
  HasLegs with KnowsName {
  def bodyTemperature: Double = 98.6
}

A Biker is a Person but may only be added to an Athlete (Listing 7-30).

Listing 7-30. Biker Trait

trait Biker extends Person {
  this: Athlete=>
  def ride() {println("I'm riding my bike")}
}

And finally, let’s define some Genders (Listing 7-31).

Listing 7-31. Defining Gender

trait Gender
trait Male extends Gender
trait Female extends Gender

We’ve defined a complex hierarchy of classes and traits. Let’s see what we can do with these classes. First, let’s try to create a  Dog that’s also a Biker:

scala> val bikerDog = new Dog("biker") with Athlete with Biker
<console>:4: error: illegal inheritance; superclass Dog
is not a subclass of the superclass Person
of the mixin trait Biker
    val bikerDog = new Dog("biker") with Athlete with Biker

Cool, the compiler enforced our rule about Bikers needing to be Persons. Let’s create some valid LivingThings. Please note that we can combine different traits as part of the object creation. So, archer is an instance of a class that is a subclass of Dog that implements Athlete, Runner, and Male. The Scala compiler automatically creates this new, anonymous class for you.

scala> val archer = new Dog("archer") with Athlete with Runner with Male
archer: Dog with Athlete with Runner with Male = $anon$1@18bbc98
scala> val dpp = new Person("David") with Athlete with Biker with Male
dpp: Person with Athlete with Biker with Male = $anon$1@7b5617
scala> val john = new Person("John") with Athlete with Runner with Male
john: Person with Athlete with Runner with Male = $anon$1@cd927d
scala> val annette = new Person("Annette") with Athlete with Runner with Female
annette: Person with Athlete with Runner with Female = $anon$1@1ec41c0

We’ve got a bunch of Animals. Let’s see what we can do with them:

scala> def goBiking(b: Biker) = println(b.name+" is biking")
goBiking: (Biker)Unit
scala> goBiking(dpp)
David is biking

What happens if we try to send Annette on a bike ride?

scala> goBiking(annette)
<console> :7: error: type mismatch;
  found   : Person with Athlete with Runner with Female
  required: Biker
        goBiking(annette)

This makes sense. The method requires a Biker, and Annette is not a Biker. However, just as we can compose a class out of traits, we can require that a class implement more than one trait in order to be the parameter to a method:

scala> def charityRun(r: Person with Runner) = r.run()
charityRun: (Person with Runner)Unit

The charityRun method can only be called with a parameter that is a subclass of Person and also implements the Runner trait.

scala> charityRun(annette)
I'm running

What if we try to call the method with a Runner that is not a Person?

scala> charityRun(archer)
<console> :7: error: type mismatch;
found     : Dog with Athlete with Runner with Male
required  : Person with Runner
      charityRun(archer)

We can define the parameter in terms of traits. The womensRun method may only be called with a parameter that’s both a Runner and a Female:

scala> def womensRun(r: Runner with Female) = r.run()
womensRun: (Runner with Female)Unit
scala> womensRun(annette)
I'm running
scala> val madeline = new Cat("Madeline") with Athlete with Runner with Female
madeline: Cat with Athlete with Runner with Female = $anon$1@11dde0c
scala> womensRun(madeline)
I'm running

In this way, we’ve modeled complex relationships. We’ve modeled things in a way that you cannot model with Java. Scala’s compositional rules are very powerful tools for defining complex class hierarchies and for specifying the rules for composing classes as well as the rules for passing parameters into methods. In this way, we can make sure that the charityRun method can only be called with valid parameters rather than testing for parameter correctness at runtime and throwing an exception if the parameter is not correct. This increased modeling flexibility combined with enhanced type safety gives the architect another tool to help developers write correct code.

Summary

This chapter has shown you how traits work and how to use them. You saw how a trait encapsulates method and field definitions, which can then be reused by mixing them into classes. You saw that traits are similar to multiple inheritance but they avoid some of the difficulties of multiple inheritance. In the next chapter, we’ll explore Collections which is the most noteworthy library in the Scala ecosystem.

____________________________

1Self types can also be used to discover at compile time what class a trait has been mixed into. See http://www.scala-lang.org/node/124.

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

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