Patterns and those aha! moments
Patterns have more to do with a software design such as how to compose things together, the kind of design template to be used, and so on. They are a lot more about interactions between classes and objects. Patterns are design recipes, and they illustrate a solution to a recurring design problem. However, idioms are largely specific to a language and patterns are more general and at a higher level. Patterns are also mostly language independent.
You can (and usually do) implement the same design patterns in the language you are working with.
The command design pattern
The command design pattern encapsulates an object. It allows you to invoke a method on the object at a later point. For example, we are used to the following code line:
Given a Java object:
a.someMethod(arg1, arg2); a.method1(arg1, arg2); a.method2(arg3);
We expect the call a.method1
to complete before the a.method2
call starts. On the other hand, consider a real life situation.
Let's say you go to a restaurant, sit at a table, and order food. The waiter scribbles down the order on a piece of paper. This piece of paper then goes to the kitchen, someone cooks the food, and the food is served. It makes sense to prepare food for someone who ordered earlier, so your order is queued.
In the preceding paragraph, the piece of paper holds the details of your order. This is the command object. The preceding description also talks about a queue where the someone who cooks is the invoker—he puts things in motion as per your command. Add the undo()
functionality and you have the essence of the command design pattern. Database transactions need to undo the commands on failure—the rollback semantics, for example.
Here is a simple first cut as example:
def command(i : Int) = println(s"---$i---") def invokeIt(f : Int => Unit) = f(1) invokeIt(command)
Note
The def
method gets converted to a function. This is called an ETA expansion. We will soon be looking at the details.
This is a bit unpalatable though. I can possibly pass any function whatsoever and possibly wreak havoc. So, to constrain the things we can possibly pass on to the invoker, we take a bit of help from the compiler by typing the following commands:
scala> case class Command(f: Int => Unit) defined class Command scala> def invokeIt(i: Int, c: Command) = c.f(i) invokeIt: (i: Int, c: Command)Unit scala> def cmd1 = Command(x => println(s"**${x}**")) cmd1: Command scala> def cmd2 = Command(x => println(s"++++${x}++++")) cmd2: Command scala> invokeIt(3, cmd1) **3** scala> invokeIt(5, cmd2) ++++5++++
It is so terse.
The strategy design pattern
Strategy helps us to define a set of algorithms, encapsulates each step, and selects one as appropriate.
Oh boy! Here it is. We surely have used the java.util.Comparator
strategy interface at times that allows us to vary the compare
algorithm as we see fit so we can sort arrays at will. Let's try the following example:
Integer[] arr = new Integer[] { 1, 3, 2, 4 }; Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare(Integer x, Integer y) { return Integer.compare(y, x); // the strategy algorithm –// for reverse sorting } }; Arrays.sort(arr, comparator); System.out.println(Arrays.toString(arr));
Scala makes it a breeze by using these strategy... Type the following command to sort an array:
scala> List(3, 7, 5, 2).sortWith(_ < _) res0: List[Int] = List(2, 3, 5, 7)
Passing algorithms around
We need this ability to plug in an algorithm as needed. When we start applying a strategy, we really try to apply the
Open/Closed principle (OCP). We don't touch the sort
algorithm internals, that is, the sort implementation is closed for modification. However, by passing in a comparator, we can use the algorithm to sort objects of various classes that are open for extension.
This open for extension feature is realized very easily in Scala, as it allows us to pass functions around.
Here's another code snippet as an example of passing functions:
def addThem(a: Int, b: Int) = a + b // algorithm 1 def subtractThem(a: Int, b: Int) = a - b // algorithm 2 def multiplyThem(a: Int, b: Int) = a * b // algorithm 3 def execute(f: (Int, Int) => Int, x: Int, y: Int) = f(x, y) println("Add: " + execute(addThem, 3, 4)) println("Subtract: " + execute(subtractThem, 3, 4)) println("Multiply: " + execute(multiplyThem, 3, 4))
Here, these various strategy algorithms are functions—no boilerplate. Imagine writing this in Java. This code is possible because in Scala, we can pass functions around as objects. Furthermore, we can use a method where a function is expected:
val divideThem = (x: Int, y: Int) => x / y println("Divide: " + execute(divideThem, 11, 5))
Scala's functions are first-class objects, meaning they can be sent to a method and returned from a method, just like a number or string. The ability to pass functions to other functions is a very powerful concept. It is one of the major pillars of FP, as we will soon see.