In Scala, traits are a basic mechanism of code reuse. A trait contains fields and methods which can then be reused by mixing them with classes. A class can mix in in any number of traits.
Defining a trait
A trait is defined just like a class, except that it is done with keyword trait.
trait Poet{ def writePoem(){ println("Twinkle Twinkle Little Star...") }}
A trait can declare a superclass,and like any other class default superclass is AnyRef.
A trait can be mixed in to a class by using keyword 'extends' or 'with'.
BallardArtist mixes in Poet by using keyword 'extends'. In this case, class implicitly inherits trait's superclass. So BallardArtist subclasses AnyRef and mixes in Poet.class BallardArtist extends Poet{ override def toString = "I write Ballard"}
Methods inherited by a class from a trait are used just like methods inherited from a superclass.
A trait also defines a type.
val ballardArtist = new BallardArtist ballardArtist.writePoem()
val poet:Poet = ballardArtist
poet.writePoem()
A class can explicitly extend a superclass with 'extends', and mix in a trait using 'with'.class BallardArtist extends Person with Poet{ override def toString = "I write Ballard"
A class can mix in multiple traits using 'with' keyword.}
class BallardArtist extends Person with Poet with ArtistA class can override methods inherited from trait.
From examples so far, one may incline to infer that Scala traits are similar to Java interfaces with concrete methods. But there is much difference between two. Traits can declare fields and have state.class BallardArtist extends Person with Poet with Artist{ override def toString = "I write Ballard" override def writePoem = { println("Don't Tell Mama I Was Drinking...") } }
Anything that can be done in a class can be done in trait too, with two differences.
First, class parameters can't be passed to a trait. Second, 'super' call in case of trait is dynamically bound and not statically bound like in class.
Interface Enrichment
One major benefit of trait is that as it can have concrete methods, it helps in enriching the interface of a class. Unlike interfaces where every implementing class has to provide implementations of the interface methods, trait can provide implementations to be reused by all classes which mix in the trait. With traits, classes need to implement only a small number of abstract methods(thin part of the interface) with trait itself implementing majority of the methods(rich part of the interface).
A very good example of this is Ordered trait in Scala. This trait provides a rich set of comparison methods like <,<=,>,>= etc. If developers want to provide have similar interface for their custom class, they only need to mix in Ordered trait and provide implementation for 'compare' method.Ordered already provides implementation for comparison methods mentioned above in terms of 'compare'.
Stackable behaviour with traits linearization
When we have a class hierarchy and mixed in traits, Scala puts all of them in a linear order. So,if there is a super call in one of them, next method invoked is one up this chain. With this arrangement, if all methods in this call chain call super except last one, we get a stackable behaviour.
In this linearization scheme, a class is always linearized before all of its super classes and mixed in traits.
Suppose, we have following inheritance in our ptogram
class BWe can write our linearization scheme as:
trait T1 extends B
trait T2 extends B
trait T3 extends T2
class A extends B with T1 with T3
L(A )= A+:L(T3)+:L(T1)+:L(B)
In this scheme, :+is sort of concatenation operand such that right operand replaces that part of left operand which it shares.
Linearization of individual participants is as follows:
L(B) = (B, AnyRef, Any)So,
L(T1) = (T1, B, AnyRef, Any)
L(T2) = (T2, B, AnyRef, Any)
L(T3) = (T3, T2, B, AnyRef, Any)
L(A) = (A)+:(T3, T2, B, AnyRef, Any)+:(T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)+:(T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)
When any of these classes and traits invokes a method via super, the implementation
invoked will be the first implementation to its right in the linearization.
Example:
abstract class IntQueue{Output is as:
def get(): Int
def put(x: Int)
}
trait Doubling extends IntQueue{
abstract override def put(x: Int){
println("In Doubling's put")
super.put(2*x)
}
}
trait Incrementing extends IntQueue{
abstract override def put(x: Int){
println("In Incrementing's put")
super.put(x + 1)
}
}
class BasicIntQueue extends IntQueue{
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) {
println("In BasicIntQueue's put")
buf += x
}
}
object StackableTraitsDemo {
def main(args: Array[String]): Unit = {
val doublingQueue = new BasicIntQueue with Doubling
doublingQueue.put(10)
println(doublingQueue.get())
val incrThendoublingQueue = new BasicIntQueue with Doubling with Incrementing
incrThendoublingQueue.put(10)
println(incrThendoublingQueue.get())
val doublingThenIncrQueue = new BasicIntQueue with Incrementing with Doubling
doublingThenIncrQueue.put(10)
println(doublingThenIncrQueue.get())
}
}
In Doubling's putIn BasicIntQueue's put20In Incrementing's putIn Doubling's putIn BasicIntQueue's put22In Doubling's putIn Incrementing's putIn BasicIntQueue's put21
No comments:
Post a Comment