Traits are halfway between Java interfaces and abstract classes.
· Like abstract classes, traits may contain concrete members, but may not be instantiated.
· As with interfaces, a class can inherit the members of multiple traits.
· Classes can extend traits
class C extends Trait1 { ... }
· Traits can extend classes and other traits
trait T1 extends Class1 { ... }
trait T2 extends Trait1 { ... }
· Classes, objects, and traits can mix in traits
class C extends Trait1 with Trait2 with Trait3 { ... }
val obj = new Class1() with Trait1 with Trait2 with Trait3 { ... }
Here's a test-driver for Alpha:
object TestAlpha extends App {
var exp:
Expression = Sum(Number(42), Product(Number(3.14), Number(2.71)))
println("value = " + exp.execute)
exp = Product(Number(2),
Product(Number(3), Number(5)))
println("value = " + exp.execute)
}
It produces the output:
value = Number(50.48)
value = Number(30.0)
Here's a partial design of Alpha:
Like most programming languages, Alpha distinguishes between expressions (things that can be executed) and values (results of executing expressions). Scala formalizes this distinction by defining two traits:
trait Value
trait Expression {
def execute: Value
}
Numbers and Booles are both, expressions and values, When executed they return themselves! In future versions of Alpha there may be additional expression-value hybrids such as strings and characters. Alpha calls all such hybrids literals:
trait Literal extends Value with Expression {
def execute = this
}
class Number (val value: Double) extends Literal {
override def toString = value.toString
}
object Number {
def apply(value: Double) = new
Number(value)
}
class Boole (val value: Boolean) extends Literal {
override def toString = value.toString
}
object Boole {
def apply(value: Boolean) = new
Boole(value)
}
By contrast, a sum is an expression but not a value:
class Sum(val operand1: Expression, val operand2: Expression)
extends Expression {
def execute = {
val arg1 = operand1.execute
val arg2 = operand2.execute
if (!arg1.isInstanceOf[Number] ||
!arg2.isInstanceOf[Number]) {
throw new Exception("sum
inputs must be numbers")
}
val num1 = arg1.asInstanceOf[Number]
val num2 = arg2.asInstanceOf[Number]
new Number(num1.value + num2.value)
}
}
// and a companion object
object Sum {
def apply(operand1: Expression,
operand2: Expression) = new Sum(operand1, operand2)
}
Delegation chains are useful when we want to add behavior to an object.
Assume Delegate is a trait with a concrete do-nothing method:
trait
Delegate {
def delegate() {}
}
An
Entity class inherites the do-nothing delegate method, which it calls:
class
Entity extends Delegate {
def doSomething() {
println("calling Entity.doSomething")
delegate()
}
}
Next
we define three sub-traits. Each prints a unique message, then calls
super.delegate:
trait
Delegate1 extends Delegate {
override def delegate() {
println("calling
Delegate1.delegate")
super.delegate()
}
}
//Delegte2,
Delegate3, etc.
Scala
allows objects to inherit from traits, too:
val
test1 = new Entity() with Delegate1 with Delegate2 with Delegate3
test1.doSomething()
val test2 = new Entity() with Delegate3 with Delegate1 with Delegate2
test2.doSomething()
Here's
the output. Note the delegation order:
calling
Entity.doSomething
calling Delegate3.delegate
calling Delegate2.delegate
calling Delegate1.delegate
calling Entity.doSomething
calling Delegate2.delegate
calling Delegate1.delegate
calling Delegate3.delegate