Monday, June 24, 2013

Stackable modifications in Scala using Traits

abstract class Product {
def get:Double
def put(x:Double)
}
class Computer extends Product {
var price:Double = 0.0
def get = price
def put(x:Double) = price = x
}
class KeyBoard extends Product {
var price:Double = 0.0
def get = price
def put(x:Double) = price = x
}
class PriceProcessor {
var prods:List[Product] = List()
def +(x:Product) = prods::=x

def totalPrice:Double = {
var pri:Double = 0.0
for(prod<-prods) {
pri+=prod.get
}
pri
}
}
trait ApplyDiscount extends Product{
abstract override def put(x:Double) = super.put(x*0.8)
}
trait AddTax extends Product {
abstract override def put(x:Double) = super.put(x - x*0.07)
}
object MixinTraits {

def myTest {

val prod1 = new Computer
prod1.put(1200)
val prod2 = new KeyBoard
prod2.put(80)

val proc1 = new PriceProcessor
proc1+prod1
proc1+prod2

val price1 = proc1.totalPrice
println("PRICE-->"+price1)

val proc2 = new PriceProcessor
val prod3 = new Computer with ApplyDiscount
prod3.put(1200)
val prod4 = new KeyBoard with ApplyDiscount
prod4.put(80)
proc2+prod3
proc2+prod4
val price2 = proc2.totalPrice

println("PRICE WITH DISCOUNT-->"+price2)
val proc3 = new PriceProcessor
val prod5 = new Computer with ApplyDiscount with AddTax
prod5.put(1200)
val prod6 = new KeyBoard with ApplyDiscount with AddTax
prod6.put(80)
proc3+prod5
proc3+prod6
val price3 = proc3.totalPrice

println("PRICE WITH DISCOUNT AND TAX-->"+price3)
}
}
MixinTraits.myTest


//OUTPUT
PRICE-->1280.0
PRICE WITH DISCOUNT-->1024.0
PRICE WITH DISCOUNT AND TAX-->952.32


Concepts:

Mixin Traits: Traits can be used to achieve polymorphism by using it to change the behavior of an object at runtime.
Traits are not just similar to interfaces in java but also have more attributes than interfaces.
Traits can also have concrete methods defined which can be inherited by classes/traits.

eg: abstract class A
   trait T1 extends A
   class B extends A with T1
 
Stackable modifications: A class object can achieve multilevel transformations (or decorations) by mixing it with one or
more traits. The "with" keyword needs to precede each trait the class mixes with. These traits are stacked one
upon the other starting from the right, with the right most being on the top of the stack. The trait get applied
to the class starting from the right and proceeds left ultil all the traits have been used to decorate the class.

As an example:
   abstract class A
   trait T2 extends A
   trait T1 extends A
   class B extends A with T1 with T2
 
   The trait T2 is first applied and then the trait T1 is applied to the class B.
 

Rules to make traits stackable

1) Consider the example:
abstract class A

trait T1 extends A

class B extends A with T1
class C extends A with T1

   The classes B and C extend the abstract class A. Therefore the trait (T1) mixing classes B and C also
   has to extend the abstract class A.

2) The method signature in the trait which is used to change the behaviour/state of the class which it mixes with,
   has to precede with "abstract override"

eg: abstract override def put(x:Double)

3) Mixing a trait with a class can be done in two ways.

a) While creating the class signature:
class A extends T1 //extending a trait

b) While creating the class instance
val val1 = new class A with T1 //mixing a trait


Code explanation:

1) Product is an abstract class with abstract get and put methods.
2) There are two concrete implementations of Product, namely Computer and Keyboard. These
   classes give concrete implementations of get and put methods.
3) The class PriceProcessor calculates the prices of products from a list. The totalPrice method
   iterates through each product from the list and adds the price of it to a variable "pri".
4) the traits ApplyDiscount and AddTax are used to mix with the classes Computer and Keyboard.

Client Code: (object MixingTraits)
//NORMAL CLASS BEHAVIOR
1) prod1 and prod2 are objects of classes Computer and KeyBoard respectively. The put method on these objects are used to set the prices. These objects prod1 and prod2 are added to an instance proc1 of
  the PriceProcessor class using the "+" function. This function adds appends products to a local list
  object prods present in the PriceProcessor class.
2) The totalPrice method on the PriceProcessor object proc1 is called and the total price of a Computer and a Keyboard is calculated.

//MIXING CLASS OBJECTS WITH TRAITS
3) prod3 and prod4 are objects of classes Computer and Keyboard respectively. The interesting thing is
  the mixing of a trait ApplyDiscount to the instances created.
  val prod3 = new Computer with ApplyDiscount
  val prod4 = new KeyBoard with ApplyDiscount
 
  Here the call super.put(x*0.8) delegates the call to the trait/class towards its left.
 
  abstract override def put(x:Double) = super.put(x*0.8)
 
  The discount get applied to the object mixing with this trait.

//STACKING TRAITS
4) prod5 and prod6 are objects that mix with more than one trait namely ApplyDiscount and AddTax.
  At first the AddTax trait is applied and delegated to ApplyDiscount and finally to the class mixing these traits.
 val prod5 = new Computer with ApplyDiscount with AddTax
 val prod6 = new KeyBoard with ApplyDiscount with AddTax

 The flow:
 super.put(x-x*0.07)->super.put(x*0.8)->put(x)

No comments:

Post a Comment