import java.io.PrintWriter
trait Destination {
val str:String
def sendMess
}
class Towers(f:(String)=>Unit, valToSend:String) extends Destination {
val str = valToSend
def sendMess {
f(str)
}
}
class Churchill(f:(String)=>Unit, valToSend:String) extends Destination {
val str = valToSend
def sendMess {
f(str)
}
}
class MyClass(val str:String)
class Sender{
var dests:List[Destination] = List()
def +(x:Destination) = dests::=x
def sendMessages = for(dest<-dests) dest.sendMess
}
object MyObj {
def myTest {
import MyConversions._
val dest1 = new Towers(printContent, "Hi this is Towers")
val dest2 = new Churchill(printContent, "Hi this is Churchill")
var send = new Sender
send+dest1
send+dest2
send.sendMessages
var send1 = new Sender
val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1"))
val dest4 = new Churchill(printContent, new MyClass("This is implicit Churchill message 1")) send1+dest3
send1+dest4
send1.sendMessages
}
def printContent(x:String):Unit= {
val pw = new PrintWriter("testObserver.txt", "UTF-8")
pw.write(x) pw.flush
}
}
object MyConversions {
implicit def conToStr(x:MyClass):String = x.str
}
MyObj.myTest
trait Destination {
val str:String
def sendMess
}
class Towers(f:(String)=>Unit, valToSend:String) extends Destination {
val str = valToSend
def sendMess {
f(str)
}
}
class Churchill(f:(String)=>Unit, valToSend:String) extends Destination {
val str = valToSend
def sendMess {
f(str)
}
}
class MyClass(val str:String)
class Sender{
var dests:List[Destination] = List()
def +(x:Destination) = dests::=x
def sendMessages = for(dest<-dests) dest.sendMess
}
object MyObj {
def myTest {
import MyConversions._
val dest1 = new Towers(printContent, "Hi this is Towers")
val dest2 = new Churchill(printContent, "Hi this is Churchill")
var send = new Sender
send+dest1
send+dest2
send.sendMessages
var send1 = new Sender
val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1"))
val dest4 = new Churchill(printContent, new MyClass("This is implicit Churchill message 1")) send1+dest3
send1+dest4
send1.sendMessages
}
def printContent(x:String):Unit= {
val pw = new PrintWriter("testObserver.txt", "UTF-8")
pw.write(x) pw.flush
}
}
object MyConversions {
implicit def conToStr(x:MyClass):String = x.str
}
MyObj.myTest
EXPLANATION:
The example I tried to show here is the Observer core design pattern.
Observer Pattern: In this pattern there will be a single source point but multiple destination points. Each destination point is unaware of one another. Therefore they are decoupled and hence addition of new destinations can be done and therefore it enhances maintainability without much rework.
Examples of Observer patterns are
1) Cellphone tower: As soon as the tower bandwidth is up, it notifies all the cellphones within its range.
2) Logs in a web application: We can have our log statement go multiple locations (Application logs, System logs etc..)
The trait Destination encompasses the abstract fields/methods that the concrete destinations implement.
In this example the destinations are 1) Towers & 2) Churchill. These were the two apartments in Minneapolis where I stayed. Each of these classes has a sendMess method which does some work.
There are two parameters of this method. One is a function literal and the other is a String.
A function literal is closely similar to anonymous classes in java.
Java Anonymous Class: When we do not want to have a sub-class , when we want to have a inline class implementation , when we want to reduce verbosity by writing smaller classes in separate files, then we can use an anonymous class.
Eg: Consider an Interface Product which has a single method getName() which returns a String. Consider a client which is trying to use a concrete implementation of Product but does not like it having sub-classed for the above mentioned reasons.
public interface Product{
public String getName();
}
// Method which needs a product type parameter.
public void myMethod(Product prod){
}
//Client code which calls the myMethod method using Anonymous Functions
myMethod(new Product{
@Override // Just to make sure we are overriding the method indeed.
public String getName() {
return "Anonymous Product" ;
}
});
// Client code which calls the myMethod method normally (without anonymous classes)
public class ProductImpl extends Product {
@Override
public String getName() {
return "Anonymous Product";
}
}
Product prod = new ProductImpl();
myMethod(prod);
The above code increases verbosity and since the class implementation has just one small method , it need not be sub-classed.
Function Literal: On the other hand, a function literal in Scala need not have an enclosing wrapper (unlike anonymous classes in java ) to be able to survive. It is of the form func:(T,M)=>K. Here the function literal's reference is 'func' which takes parameters T & M as input and returns a value of type K. Here the part towards the right side of "=>" is its body and the part towards the left of "=>" are its input parameters. The function's parameters and the body have to be supplied by the client calling the function literal. The function literal can be easily passed as parameters to other functions, as return values, as variable types and even as Type Parameters for eg: MyClass[f:(Int,Float):Boolean] is a class with a Type parameter f:(Int,Float):Boolean. Though we have type parameters in java of the form MyClass<T> , T cannot be a function literal as the Java compiler does not allow that.
As part of Java 8 , there is a Project called Project Lambda which is contemplating on bringing function literals into java and the syntax proposed is something like this.... f:(int, float) <- Boolean.
Java Anonymous Class: When we do not want to have a sub-class , when we want to have a inline class implementation , when we want to reduce verbosity by writing smaller classes in separate files, then we can use an anonymous class.
Eg: Consider an Interface Product which has a single method getName() which returns a String. Consider a client which is trying to use a concrete implementation of Product but does not like it having sub-classed for the above mentioned reasons.
public interface Product{
public String getName();
}
// Method which needs a product type parameter.
public void myMethod(Product prod){
}
//Client code which calls the myMethod method using Anonymous Functions
myMethod(new Product{
@Override // Just to make sure we are overriding the method indeed.
public String getName() {
return "Anonymous Product" ;
}
});
// Client code which calls the myMethod method normally (without anonymous classes)
public class ProductImpl extends Product {
@Override
public String getName() {
return "Anonymous Product";
}
}
Product prod = new ProductImpl();
myMethod(prod);
The above code increases verbosity and since the class implementation has just one small method , it need not be sub-classed.
Function Literal: On the other hand, a function literal in Scala need not have an enclosing wrapper (unlike anonymous classes in java ) to be able to survive. It is of the form func:(T,M)=>K. Here the function literal's reference is 'func' which takes parameters T & M as input and returns a value of type K. Here the part towards the right side of "=>" is its body and the part towards the left of "=>" are its input parameters. The function's parameters and the body have to be supplied by the client calling the function literal. The function literal can be easily passed as parameters to other functions, as return values, as variable types and even as Type Parameters for eg: MyClass[f:(Int,Float):Boolean] is a class with a Type parameter f:(Int,Float):Boolean. Though we have type parameters in java of the form MyClass<T> , T cannot be a function literal as the Java compiler does not allow that.
As part of Java 8 , there is a Project called Project Lambda which is contemplating on bringing function literals into java and the syntax proposed is something like this.... f:(int, float) <- Boolean.
Coming back to our code...
In our example the function literal f:(String)=>Unit takes a String as an input and returns nothing (Unit). Unit represent the return type of a function which returns nothing. This function literal in our code holds the implementation of the sendMess method of the classes implementing the Destination trait. The implementation of the function literal is supplied by the method printContent. This method will give the concrete implementation of what has to be done when the sendMess method is called from each of the concrete implementations of the Destination trait.
The function literal just creates a PrintWriter from the java.io package and writes the string to the writer.
def printContent(x:String):Unit= {
val pw = new PrintWriter("testObserver.txt", "UTF-8")
pw.write(x)
pw.flush
}
}
The function literal just creates a PrintWriter from the java.io package and writes the string to the writer.
def printContent(x:String):Unit= {
val pw = new PrintWriter("testObserver.txt", "UTF-8")
pw.write(x)
pw.flush
}
}
By sending the implementation (as a function literal) we are removing the duplication of code within each of the classes which extends Destination. Therefore even if another 10 new destination implementations come, the code need not change and can be easily integrated with the existing code.
Implicit conversions: The variables dest3 and dest4 should ideally parameters 1) F:(String)=>Unit and 2) String. But, these variables take 1) F:(String)=>Unit and 2) MyClass. Therefore the compiler should throw an error. But it does not happen. This is where the implicit conversions come into picture.
The moment when the compiler realizes that something wrong w..r.t the parameters has happened or it sees any compilation errors, it does not give it up and throw an error but checks if there have been any implicit definitions have been made by the user or in the Predef object.
Here in our case when the compiler comes across the code..
val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1")).
The part in red is read as a compilation error, then the compiler checks if the user has imported any implicit conversions.
The implicit conversion definition is defined in the MyConversions object with the name conToStr . The name does not matter here but the keyword implicit matters everything. This keyword tells the compiler that there is a conversion that the user is providing for an object of type MyClass. This conversion definition converts this MyClass into a String. This conversion makes the compiler successful in parsing the variable declaration dest3 and dest4. The implicit definition conToStr is defined as follows..
object MyConversions {
implicit def conToStr(x:MyClass):String = x.str
}
The definition just takes an object of MyClass and returns a String and remember to import this object for the conversion to apply.
import MyConversions._
Operator Overloading: The operator + is overloaded by the users own implementation. This is similar to adding an object to another.i.e. obj1.add(obj2) is simplified to obj1+obj2. This code adds the obj2 to obj1. The custom definition of the operator "+" is give just the same way any other function is defined.
for e.g: def methodName(Parameters) = def +(x:Destination): Unit
Here the method name is replaced with "+" and it can be used as a+b instead of a .+(b) which is the underhood implemtation which Scala provides.
CODE FLOW EXPLANATION:
1) A trait (named Destination) which abstracts the functions a destination provides is created. Provide concrete implementations to this trait with Names Churchill and Towers. These are the places where the message has to be delivered.
2) The concrete classes implement the sendMess method by calling the function literal f.
3) Create a Sender class which collects all the destinations which the user wants to send his message to. This class defines a "+" method which adds new destinations supplied by the client calling it. The method sendMessages iterates through the Destinations and calls the sendMess method on each of the destination.
4) Create MyConversions object and import it as import MyConversions._
5) The client code MyObj has a method myTest which instantiates the Churchill and Towers classes, adds them to the Sender object and calls the sendMessages on the Sender object. This will eventually call the function literal f:(String)=>Unit on each of the concrete Destination classes and the message will be delivered by writing the message to a file with the name testObserver.txt.
Advantages:
1) Enhance re usability through traits.
2) Function literals enhance robustness and make code less verbose.
3) Implicit conversions make integration with legacy java libraries easy.
4) Operator overloading makes using operators with their own syntax but custom defined implementation.
Implicit conversions: The variables dest3 and dest4 should ideally parameters 1) F:(String)=>Unit and 2) String. But, these variables take 1) F:(String)=>Unit and 2) MyClass. Therefore the compiler should throw an error. But it does not happen. This is where the implicit conversions come into picture.
The moment when the compiler realizes that something wrong w..r.t the parameters has happened or it sees any compilation errors, it does not give it up and throw an error but checks if there have been any implicit definitions have been made by the user or in the Predef object.
Here in our case when the compiler comes across the code..
val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1")).
The part in red is read as a compilation error, then the compiler checks if the user has imported any implicit conversions.
The implicit conversion definition is defined in the MyConversions object with the name conToStr . The name does not matter here but the keyword implicit matters everything. This keyword tells the compiler that there is a conversion that the user is providing for an object of type MyClass. This conversion definition converts this MyClass into a String. This conversion makes the compiler successful in parsing the variable declaration dest3 and dest4. The implicit definition conToStr is defined as follows..
object MyConversions {
implicit def conToStr(x:MyClass):String = x.str
}
The definition just takes an object of MyClass and returns a String and remember to import this object for the conversion to apply.
import MyConversions._
Operator Overloading: The operator + is overloaded by the users own implementation. This is similar to adding an object to another.i.e. obj1.add(obj2) is simplified to obj1+obj2. This code adds the obj2 to obj1. The custom definition of the operator "+" is give just the same way any other function is defined.
for e.g: def methodName(Parameters) = def +(x:Destination): Unit
Here the method name is replaced with "+" and it can be used as a+b instead of a .+(b) which is the underhood implemtation which Scala provides.
CODE FLOW EXPLANATION:
1) A trait (named Destination) which abstracts the functions a destination provides is created. Provide concrete implementations to this trait with Names Churchill and Towers. These are the places where the message has to be delivered.
2) The concrete classes implement the sendMess method by calling the function literal f.
3) Create a Sender class which collects all the destinations which the user wants to send his message to. This class defines a "+" method which adds new destinations supplied by the client calling it. The method sendMessages iterates through the Destinations and calls the sendMess method on each of the destination.
4) Create MyConversions object and import it as import MyConversions._
5) The client code MyObj has a method myTest which instantiates the Churchill and Towers classes, adds them to the Sender object and calls the sendMessages on the Sender object. This will eventually call the function literal f:(String)=>Unit on each of the concrete Destination classes and the message will be delivered by writing the message to a file with the name testObserver.txt.
Advantages:
1) Enhance re usability through traits.
2) Function literals enhance robustness and make code less verbose.
3) Implicit conversions make integration with legacy java libraries easy.
4) Operator overloading makes using operators with their own syntax but custom defined implementation.
No comments:
Post a Comment