Utilizing Object Orientation - Scala
Avoid abstract val in traits!
//The order in which thos two fields will be initialized is unknown!
trait Property {
val name: String
override val toString = """Property( $name )"""
}
The Property trait defines an abstract member 'name' which stores the current name of the property.
The 'toString' method is overriden to create a string using the 'name' member.
val x = new Property {override val name = "HI"} //java.lang.Object with Property(null)
The val x is defined as an instance of an anonymous subclass of the Property trait.
When creating this instance the REPL prints null instead of HI.
This is due to the order of initialization - the base trait Property is initialized
first during construction. Wheb the 'toString' method is looking for the value of 'name',
it hasn't been initialized yet. After taht the annonymous subclass is constructed and the
value of 'name' is initialized.
Two ways to solve it:
1. Defining 'toString' as lazy - that means that only after the annonymous object
is fully constructed, the method is called. It doesn't garantee
the initialization order though
2. Early member definition - is the other way (and prefered) for dealing with
such problem.
class X extends {val name = "HI"} with Property
or
new {val name = "HI"} with Property
* For any complicated trait hierarchies, early member initialization provide a more
elegant solution to the problem
Provide empty implementations for abstract methods in traits!
Class linearization:
Linearization is the process of specifying a linear ordering to the superclass
of a given class.In Scala this ordering changes for each subclass and is
reconstructed for classes in the hierarchy. This means that two subclasses of
some common parent could have different linearization and therefore different
behaviors.
1. Base class/trait are constructed first.
2. Traits are constructed left to right (because tait on the right is added later
and thus overrides the prevoius traits)
3. To construct a trait it's base traits must be constructed first.
4. If a trait is constructed it is never reconstructed again
5. The construction order is the reverse of linearisation.
class A {
def foo = "A"
}
trait B extends A {
override def foo = "B" + super.foo
}
trait C extends B {
override def foo = "C" + super.foo
}
trait D extends A {
override def foo = "D" + super.foo
}
var x = new A with D with C with B //CBDA
We start building the linearisation according to the order of mixins left to right.
First A is constructed as it is the first base class!
D linearisation:
A not considered as already occured before.
D extends A
C linearisation:
A not considered
B extends A
C extends B
C linearisation:
A not considered
B not considered
ADBC (Whre A is constructed first and C constructed last)
That's why the order of function executions is:
C -> B -> D -> A
Sometimes we have several implementations of a certain function in different
traits. and we would like to have the maximum flexability to choose which
implementaion we're actually using.
Scala traits have a powerful ability to address some 'super' functionallity
without declaring explicitely their parent entity.
In order to achieve that we have to do one of two things:
1. Declare a self type on the trait. This approach limits how your traits
might be mixed in.
trait A {this: B => } //A can not ne mixed into a concrete class that does not
//also extends B
2. A better approach is to define an empty implementation on the entity
where traits may start being mixed in.
trait A {
//This is our default implementation, it doesn;t have to be empty.
def foo = {}
}
trait B extends A {
override def foo = {
println("B")
super.foo
}
}
trait C extends A {
override def foo = {
println("C")
super.foo
}
}
trait D extends A {
override def foo = {
println("D")
super.foo
}
}
val x = new A with B with C with D //DCBA
val y = new A with D with C with B//BCDA
* Ofcourse if our traits extend not just a base A trait
the linearisation will be more complex.
When creating hierarchy of mixable behaviors via traits, we need to ensure the
following:
* We have a mixin point that traits can assume as a parent
* The mixable traits delegate to their parent in a meaningfull way.
* Provide a default implementation for hte chain-of-command style methods
at your mixin point.