Chapter 4. Traits
Introducing Traits
Before we dive into object-oriented programming, there’s one more essential feature of Scala that you should get acquainted with: traits. Understanding the value of this feature requires a little backstory.
In Java, a class can implement an arbitrary number of interfaces. This model is very useful for declaring that a class exposes multiple abstractions. Unfortunately, it has one major drawback.
For many interfaces, much of the functionality can be implemented with boilerplate code that will be valid for all classes that use the interface. Java provides no built-in mechanism for defining and using such reusable code. Instead, Java programmers must use ad hoc conventions to reuse implementation code for a given interface. In the worst case, the developer just copies and pastes the same code into every class that needs it.
Often, the implementation of an interface has members that are unrelated (“orthogonal”) to the rest of the instance’s members. The term mixin is often used for such focused and potentially reusable parts of an instance that could be independently maintained.
Have a look at the following code for a button in a graphical user interface, which uses callbacks for “clicks”:
// code-examples/Traits/ui/button-callbacks.scala
package
uiclass
ButtonWithCallbacks
(val
label:String
,val
clickedCallbacks:List[() => Unit]
)extends
Widget
{ require(clickedCallbacks !=null
,"Callback list can't be null!"
)def
this
(label:String
, clickedCallback: ()=>
Unit
) =this
(label,List
(clickedCallback))def
this
(label:String
) = {this
(label,Nil
) println("Warning: button has no click callbacks!"
) }def
click
() = {// ... logic to give the appearance of clicking a physical button ...
clickedCallbacks.foreach(f=>
f()) } }
There’s a lot going on here.
The primary constructor takes a label
argument and a
list of callbacks
that are invoked when the button’s
click
method is invoked. We’ll explore this class in
greater detail in Chapter 5. For
now, we want to focus on one particular problem. Not only does
ButtonWithCallbacks
handle behaviors essential to
buttons (like clicking), it also handles notification of click events by
invoking the callback functions. This goes against the Single
Responsibility Principle (see [Martin2003]), a means to the design goal
of separation of concerns. We would like to separate
the button-specific logic from the callback logic, such that each logical
component becomes simpler, more modular, and more reusable. The callback
logic is a good example of a mixin.
This separation is difficult to do in Java, even if we define an interface for the callback behavior. We still have to embed the implementation code in the class somehow, compromising modularity. The only other alternative is to use a specialized tool like aspect-oriented programming (AOP; see [AOSD]), as implemented by AspectJ (see [AspectJ]), an extension of Java. AOP is primarily designed to separate the implementations of “pervasive” concerns that are repeated throughout an application. It seeks to modularize these concerns, yet enable the fine-grained “mixing” of their behaviors with other concerns, including the core domain logic of the application, either at build or runtime.
Traits As Mixins
Scala provides a complete mixin solution, called traits. In our example, we can define the callback abstraction in a trait, as in a Java interface, but we can also implement the abstraction in the trait (or a derived trait). We can declare classes that “mix in” the trait, much the way you can declare classes that implement an interface in Java. However, in Scala we can even mix in traits at the same time we create instances. That is, we don’t have to declare a class first that mixes in all the traits we want. So, Scala traits preserve separation of concerns while giving us the ability to compose behavior on demand.
If you come from a Java background, you can think of traits as interfaces with optional implementations. Or, if you prefer, you can think of traits as a “constrained” form of multiple inheritance. Other languages provide constructs that are similar to traits, such as modules in Ruby, for example.
Let’s use a trait to separate the callback handling from the button logic. We’ll generalize our approach a little bit. Callbacks are really a special case of the Observer Pattern (see [GOF1995]). So, let’s create a trait that implements this pattern, and then use it to handle callback behavior. To simplify things, we’ll start with a single callback that counts the number of button clicks.
First, let’s define a
simple Button
class:
// code-examples/Traits/ui/button.scala
package
uiclass
Button
(val
label:String
)extends
Widget
{def
click
() = {// Logic to give the appearance of clicking a button...
} }
Here is the parent class,
Widget
:
// code-examples/Traits/ui/widget.scala
package
uiabstract
class
Widget
The logic for managing
callbacks (i.e., the clickedCallbacks
list) is
omitted, as are the two auxiliary constructors. Only the button’s
label
field and click
method
remain. The click
method now only cares about the
visual appearance of a “physical” button being clicked.
Button
has only one concern, handling the “essence”
of being a button.
Here is a trait that implements the logic of the Observer Pattern:
// code-examples/Traits/observer/observer.scala
package
observertrait
Subject
{type
Observer
= {def
receiveUpdate
(subject:Any
) }private
var
observers =List
[Observer]
()def
addObserver
(observer:Observer
) = observers ::= observerdef
notifyObservers
= observers foreach (_
.receiveUpdate(this
)) }
Except for the
trait
keyword, Subject
looks like
a normal class. Subject
defines all the members it
declares. Traits can declare abstract members,
concrete members, or both, just as classes can (see
Overriding Members of Classes and Traits for more details). Also like
classes, traits can contain nested trait and class definitions, and
classes can contain nested trait definitions.
The first line defines a
type
for an Observer
. This is a
structural type of the form { def receiveUpdate(subject:Any) }
.
Structural types specify only the structure a type must support; you
could think of them as “anonymous” types.
In this case, the
structural type is defined by a method with a particular signature. Any
type that has a method with this signature can be used as an observer.
We’ll learn more about structural types in Chapter 12. If you’re wondering why we didn’t use
Subject
as the type of the argument, instead of
Any
, we’ll revisit that issue in Self-Type Annotations and Abstract Type Members.
The main thing to notice
for now is how this structural type minimizes the coupling between the
Subject
trait and any potential users of the
trait.
Note
Subject
is still coupled by the name of the
method in Observer
through the structural type,
i.e., to a method named receiveUpdate
. There are
several ways we can reduce this remaining coupling. We’ll see how in
Overriding Abstract Types.
Next, we declare a list of
observers. We make it a var
, rather than a
val
, because List
is immutable, so
we must create a new list when an observer is added using the addObserver
method.
We’ll discuss Scala
List
s more in The Scala Type Hierarchy
and also in Chapter 8. For now, notice
that addObserver
uses the list cons “operator” method
(::
) to
prepend an observer
to the list
of observers
. The scala
compiler
is smart enough to turn the following statement:
observers
::=
observer
observers
=
observer
::
observers
Note that we wrote
observer :: observers
, with the existing
observers
list on the righthand
side. Recall that any method that ends with :
binds
to the right. So, the previous statement is
equivalent to the following statement:
observers
=
observers
.::(
observer
)
The
notifyObservers
method iterates through the
observers
, using the foreach
method and calls receiveUpdate
on each one. (Note
that we are using the “infix” operator notation instead of
observers.foreach
.) We use the placeholder
_
to shorten the following expression:
(obs)=>
obs.receiveUpdate(this
)
_
.receiveUpdate(this
)
This expression is actually the body of an “anonymous function,” called a function literal in Scala. This is similar to a lambda and like constructs used in many other languages. Function literals and the related concept of a closure are discussed in Function Literals and Closures.
In Java, the
foreach
method would probably take an interface, and
you would pass an instance of a class that implements the interface
(e.g., the way Comparable
is typically used).
In Scala, the
List[A].foreach
method expects an argument of type
(A) => Unit
, which is a function taking an
instance of type A
—where A
represents the type of the elements of the list
(Observer
, in this case)—and returning
Unit
(like void
in
Java).
Note
We chose to use a var
with immutable
Lists
for the observers in this example. We could
have used a val
with a mutable type, like ListBuffer
. That choice would make a
little more sense for a real application, but we wanted to avoid the
distraction of explaining new library classes.
Once again, we learned a
lot of Scala from a small example. Now let’s put our Subject
trait to use. Here is
ObservableButton
, which subclasses
Button
and mixes in Subject
:
// code-examples/Traits/ui/observable-button.scala
package
uiimport
observer._class
ObservableButton
(name:String
)extends
Button
(name)with
Subject
{override
def
click
() = {super
.click() notifyObservers } }
We start by importing
everything in the observer
package, using the
_
wildcard. Actually, we have only defined the
Subject
trait in the package.
The new class uses the
with
keyword to add the Subject
trait to the class. ObservableButton
overrides the
click
method. Using the super
keyword (see Overriding Abstract and Concrete Methods), it first invokes the
“superclass” method, Button.click
, and then it notifies the
observers. Since the new click
method overrides
Button
’s concrete implementation, the
override
keyword is required.
The with
keyword is analogous to Java’s implements
keyword for
interfaces. You can specify as many traits as you want, each with its
own with
keyword.
A class can extend a
trait, and a trait can extend a class. In fact, our
Widget
class earlier could have been declared to be a
trait.
Note
If you declare a class that uses one or more traits and it
doesn’t extend another class, you must use the
extends
keyword for the first trait listed.
If you don’t use
extends
for the first trait, e.g., you write the
following:
// ERROR:
class
ObservableButton
(name:String
)with
Button
(name)with
Subject
{...}
You’ll get an error like this:
... error: ';' expected but 'with' found. class ObservableButton(name: String) with Button(name) with Subject {...} ^
The error should really
say, “with
found, but extends
expected.”
To demonstrate this code, let’s start with a class for observing button clicks that simply counts the number of clicks:
// code-examples/Traits/ui/button-count-observer.scala
package
uiimport
observer._class
ButtonCountObserver
{var
count =0
def
receiveUpdate
(subject:Any
) = count +=1
}
Finally, let’s write a
test that exercises all these classes. We will use the Specs library
(discussed in Specs) to write a
Behavior-Driven Development ([BDD]) “specification” that exercises
the combined Button
and Subject
types:
// code-examples/Traits/ui/button-observer-spec.scala
package
uiimport
org.specs._import
observer._object
ButtonObserverSpec
extends
Specification
{"A Button Observer"
should {"observe button clicks"
in {val
observableButton =new
ObservableButton
("Okay"
)val
buttonObserver =new
ButtonCountObserver
observableButton.addObserver(buttonObserver)for
(i<-
1
to3
) observableButton.click() buttonObserver.count mustEqual3
} } }
If you downloaded the
code examples from the
O’Reilly site, you can follow the directions in its
README files for building and running the examples
in this chapter. The output of the specs
“target” of
the build should include the following text:
Specification "ButtonCountObserverSpec" A Button Observer should + observe button clicks Total for specification "ButtonCountObserverSpec": Finished in 0 second, 10 ms 1 example, 1 expectation, 0 failure, 0 error
Notice that the strings
A Button Observer should
and observe button
clicks
correspond to strings in the example. The output of a
Specs run provides a nice summary of the
requirements for the items being tested, assuming good choices were made
for the strings.
The body of the test
creates an “Okay” ObservableButton
and a
ButtonCountObserver
, which gives the observer to the
button. The button is clicked three times, using the
for
loop. The last line requires the observer’s
count
to equal 3. If you are accustomed to using an
XUnit-style TDD tool, like JUnit
(see [JUnit]) or
ScalaTest
(see [ScalaTestTool] and ScalaTest), then the last line is equivalent to the
following JUnit
assertion:
assertEquals(3
, buttonObserver.count)
Note
The Specs library (see Specs) and the ScalaTest library (see ScalaTest) both support Behavior-Driven Development ([BDD]), a style of Test-Driven Development ([TDD]) that emphasizes the “specification” role of tests.
Suppose we need only one
ObservableButton
instance? We actually don’t have to
declare a class that subclasses Button
with
Subject
. We can incorporate the trait when we create
the instance.
The next example shows a
revised Specs file that instantiates a Button
with
Subject
mixed in as part of the declaration:
// code-examples/Traits/ui/button-observer-anon-spec.scala
package
uiimport
org.specs._import
observer._object
ButtonObserverAnonSpec
extends
Specification
{"A Button Observer"
should {"observe button clicks"
in {val
observableButton =new
Button
("Okay"
)with
Subject
{override
def
click
() = {super
.click() notifyObservers } }val
buttonObserver =new
ButtonCountObserver
observableButton.addObserver(buttonObserver)for
(i<-
1
to3
) observableButton.click() buttonObserver.count mustEqual3
} } }
The revised declaration of
observableButton
actually creates an anonymous class
in which we override the click
method, as before. The
main difference with creating anonymous classes in Java is that we can
incorporate traits in this process. Java does not let you implement a
new interface while instantiating a class.
Finally, note that the inheritance hierarchy for an instance can be complex if it mixes in traits that extend other traits, etc. We’ll discuss the details of the hierarchy in Linearization of an Object’s Hierarchy.
Stackable Traits
There are a couple of refinements we can do to improve the reusability of our work and to make it easier to use more than one trait at a time, i.e., to “stack” them.
First, let’s introduce a new
trait, Clickable
, an abstraction for any widget that
responds to clicks:
// code-examples/Traits/ui2/clickable.scala
package
ui2trait
Clickable
{def
click
() }
Note
We’re starting with a new package, ui2
, to make
it easier to keep older and newer versions of the examples distinct in
the downloadable code.
The
Clickable
trait looks just like a Java interface; it is
completely abstract. It defines a single, abstract method,
click
. The method is abstract because it has no body.
If Clickable
were a class, we would have to add the
abstract
keyword in front of the
class
keyword. This is not necessary for traits.
Here is the refactored button, which uses the trait:
// code-examples/Traits/ui2/button.scala
package
ui2import
ui.Widgetclass
Button
(val
label:String
)extends
Widget
with
Clickable
{def
click
() = {// Logic to give the appearance of clicking a button...
} }
This code is like Java code
that implements a Clickable
interface.
When we previously defined
ObservableButton
(in Traits As Mixins), we overrode Button.click
to notify the observers. We had to duplicate that logic in ButtonObserverAnonSpec
when we declared
observableButton
as a Button
instance that mixed in the Subject
trait directly.
Let’s eliminate this duplication.
When we refactor the code
this way, we realize that we don’t really care about observing buttons; we
care about observing clicks. Here is a trait that focuses solely on
observing Clickable
:
// code-examples/Traits/ui2/observable-clicks.scala
package
ui2import
observer._trait
ObservableClicks
extends
Clickable
with
Subject
{abstract
override
def
click
() = {super
.click() notifyObservers } }
The
ObservableClicks
trait extends
Clickable
and mixes in Subject
. It
then overrides the click
method with an implementation
that looks almost the same as the overridden method shown in Traits As Mixins. The important difference is the
abstract
keyword.
Look closely at this method.
It calls super.click()
, but what is
super
in this case? At this point, it could only appear
to be Clickable
, which declares
but does not define the click
method, or it could be Subject
, which doesn’t have a
click
method. So, super
can’t be
bound, at least not yet.
In fact,
super
will be bound when this trait is mixed into an
instance that defines a concrete click
method, such as
Button
. Therefore, we need an
abstract
keyword on
ObservableClicks.click
to tell the compiler (and the
reader) that click
is not yet fully implemented, even
though ObservableClicks.click
has a body.
Note
Except for declaring abstract classes, the
abstract
keyword is only required on a method in a
trait when the method has a body, but it calls the
super
method that doesn’t have a concrete
implementation in parents of the
trait.
Let’s use this trait with
Button
and its concrete click
method
in a Specs test:
// code-examples/Traits/ui2/button-clickable-observer-spec.scala
package
ui2import
org.specs._import
observer._import
ui.ButtonCountObserverobject
ButtonClickableObserverSpec
extends
Specification
{"A Button Observer"
should {"observe button clicks"
in {val
observableButton =new
Button
("Okay"
)with
ObservableClicks
val
buttonClickCountObserver =new
ButtonCountObserver
observableButton.addObserver(buttonClickCountObserver)for
(i<-
1
to3
) observableButton.click() buttonClickCountObserver.count mustEqual3
} } }
Compare this code to
ButtonObserverAnonSpec
. We instantiate a
Button
with the ObservableClicks
trait mixed in, but now
there is no override of click
required. Hence, this
client of Button
doesn’t have to worry about properly
overriding click
. The hard work is already done by
ObservableClicks
. The desired behavior is
composed declaratively when needed.
Let’s finish our example by adding a second trait. The JavaBeans specification (see [JavaBeansSpec]) has the idea of “vetoable” events, where listeners for changes to a JavaBean can veto the change. Let’s implement something similar with a trait that vetoes more than a set number of clicks:
// code-examples/Traits/ui2/vetoable-clicks.scala
package
ui2import
observer._trait
VetoableClicks
extends
Clickable
{val
maxAllowed =1
// default
private
var
count =0
abstract
override
def
click
() = {if
(count < maxAllowed) { count +=1
super
.click() } } }
Once again, we override the
click
method. As before, the override must be declared
abstract
. The maximum allowed number of clicks defaults
to 1. You might wonder what we mean by “defaults” here. Isn’t the field
declared to be a val
? There is no constructor defined
to initialize it to another value. We’ll revisit these questions in Overriding Members of Classes and Traits.
This trait also declares a
count
variable to keep track of the number of clicks
seen. It is declared
private
, so it is invisible outside the trait (see
Visibility Rules). The overridden
click
method increments count
. It
only calls the super.click()
method if the count is
less than or equal to the maxAllowed
count.
Here is a Specs object that
demonstrates ObservableClicks
and
VetoableClicks
working together. Note that a separate
with
keyword is required for each trait, as opposed to
using one keyword and separating the names with commas, as Java does for
implements
clauses:
// code-examples/Traits/ui2/button-clickable-observer-vetoable-spec.scala
package
ui2import
org.specs._import
observer._import
ui.ButtonCountObserverobject
ButtonClickableObserverVetoableSpec
extends
Specification
{"A Button Observer with Vetoable Clicks"
should {"observe only the first button click"
in {val
observableButton =new
Button
("Okay"
)with
ObservableClicks
with
VetoableClicks
val
buttonClickCountObserver =new
ButtonCountObserver
observableButton.addObserver(buttonClickCountObserver)for
(i<-
1
to3
) observableButton.click() buttonClickCountObserver.count mustEqual1
} } }
The expected observer count
is 1. The observableButton
is declared as
follows:
new
Button
("Okay"
)with
ObservableClicks
with
VetoableClicks
We can infer that the
click
override in VetoableClicks
is
called before the click
override
in ObservableClicks
. Loosely speaking, since our
anonymous class doesn’t define click
itself, the method
lookup proceeds right to left, as declared. It’s
actually more complicated than that, as we’ll see later in Linearization of an Object’s Hierarchy.
In the meantime, what happens if we use the traits in the reverse order?
// code-examples/Traits/ui2/button-vetoable-clickable-observer-spec.scala
package
ui2import
org.specs._import
observer._import
ui.ButtonCountObserverobject
ButtonVetoableClickableObserverSpec
extends
Specification
{"A Vetoable Button with Click Observer"
should {"observe all the button clicks, even when some are vetoed"
in {val
observableButton =new
Button
("Okay"
)with
VetoableClicks
with
ObservableClicks
val
buttonClickCountObserver =new
ButtonCountObserver
observableButton.addObserver(buttonClickCountObserver)for
(i<-
1
to3
) observableButton.click() buttonClickCountObserver.count mustEqual3
} } }
Now the expected observer
count is 3. ObservableClicks
now has precedence over
VetoableClicks
, so the count of clicks is incremented,
even when some clicks are subsequently vetoed!
So, the order of declaration matters, which is important to remember for preventing unexpected behavior when traits impact each other. Perhaps another lesson to note is that splitting objects into too many fine-grained traits can obscure the order of execution in your code!
Breaking up your application into small, focused traits is a powerful way to create reusable, scalable abstractions and “components.” Complex behaviors can be built up through declarative composition of traits. We will explore this idea in greater detail in Scalable Abstractions.
Constructing Traits
Traits don’t support auxiliary constructors, nor do they accept an argument list for the primary constructor, the body of a trait. Traits can extend classes or other traits. However, they can’t pass arguments to the parent class constructor (even literal values), so traits can only extend classes that have a no-argument primary or auxiliary constructor.
However, like classes, the body of a trait is executed every time an instance is created that uses the trait, as demonstrated by the following script:
// code-examples/Traits/trait-construction-script.scala
trait
T1
{ println(" in T1: x = "
+ x )val
x=1
println(" in T1: x = "
+ x ) }trait
T2
{ println(" in T2: y = "
+ y )val
y="T2"
println(" in T2: y = "
+ y ) }class
Base12
{ println(" in Base12: b = "
+ b )val
b="Base12"
println(" in Base12: b = "
+ b ) }class
C12
extends
Base12
with
T1
with
T2
{ println(" in C12: c = "
+ c )val
c="C12"
println(" in C12: c = "
+ c ) } println("Creating C12:"
)new
C12
println("After Creating C12"
)
Running this script with the
scala
command yields the following output:
Creating C12: in Base12: b = null in Base12: b = Base12 in T1: x = 0 in T1: x = 1 in T2: y = null in T2: y = T2 in C12: c = null in C12: c = C12 After Creating C12
Notice the order of invocation of the class and trait constructors.
Since the declaration of C12
is extends Base12
with T1 with T2
, the order of construction for this simple class
hierarchy is left to right, starting with the base class
Base12
, followed by the traits T1
and T2
, and ending with the C12
constructor body. (For constructing arbitrarily complex hierarchies, see Linearization of an Object’s Hierarchy.)
So, while you can’t pass
construction parameters to traits, you can initialize fields with default
values or leave them abstract. We actually saw this before in our
Subject
trait, where the
Subject.observers
field was initialized to an empty
list.
If a concrete field in a trait does not have a suitable default value, there is no “fail-safe” way to initialize the value. All the alternative approaches require some ad hoc steps by users of the trait, which is error-prone because they might do it wrong or forget to do it all. Perhaps the field should be left abstract, so that classes or other traits that use this trait are forced to define the value appropriately. We’ll discuss overriding abstract and concrete members in detail in Chapter 6.
Another solution is to move that field to a separate class, where the construction process can guarantee that the correct initialization data is supplied by the user. It might be that the whole trait should actually be a class instead, so you can define a constructor for it that initializes the field.
Class or Trait?
When considering whether a “concept” should be a trait or a class, keep in mind that traits as mixins make the most sense for “adjunct” behavior. If you find that a particular trait is used most often as a parent of other classes, so that the child classes behave as the parent trait, then consider defining the trait as a class instead, to make this logical relationship more clear. (We said behaves as, rather than is a, because the former is the more precise definition of inheritance, based on the Liskov Substitution Principle—see [Martin2003], for example.)
Tip
Avoid concrete fields in traits that can’t be initialized to suitable default values. Use abstract fields instead, or convert the trait to a class with a constructor. Of course, stateless traits don’t have any issues with initialization.
It’s a general principle of good object-oriented design that an instance should always be in a known valid state, starting from the moment the construction process finishes.
Recap and What’s Next
In this chapter, we learned how to use traits to encapsulate and share cross-cutting concerns between classes. We covered when and how to use traits, how to “stack” multiple traits, and the rules for initializing values within traits.
In the next chapter, we explore how the fundamentals of object-oriented programming work in Scala. Even if you’re an old hand at object-oriented programming, you’ll want to read the next several chapters to understand the particulars of Scala’s approach to OOP.
Get Programming Scala now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.