Creating objects seems like the simplest of things to beginner programmers. With experience engineers realize that there can be a number of tricky aspects to the creation of objects. Sometimes the creation can be very complex. Sometimes you need to limit the number of instances of a given type are created. Object-oriented programming brings its own wrinkles. Highly abstracted code can be written to work without detailed knowledge of objects, and so being able to create such objects without detailed prior knowledge about them becomes valuable. That is one of the ideas behind the first of the creational patterns we will look at, the factory method.
The Factory Method patterns allows you to define an interface for creating various subclasses of a given class. Let's use an example to explain what this means in practice. For our example, we will create an abstraction for different types of polygons. We will then create a factory that will take the vertices of a polygon and create the appropriate type of polygon. We will show this using classical object-oriented programming first and then examine how to accomplish the same thing with a functional programming approach. The first thing we will need for our example is a class to represent a point in two-space.
class Point(var x:Double, var y:Double){ import java.lang.Math.sqrt def distanceTo(p:Point) = { var dx = x - p.x var dy = y - p.y sqrt(dx*dx + dy*dy) } }
Our class has an x and y coordinate and defines a single method, distanceTo
. This gives us the distance between our point and some other point p. Now let's define our interface for polygons.
trait Polygon{ def numSides:Int def perimeter:Double }
Our interface is very simple, saying a Polygon has two methods numSides
and perimeter
. We could add more methods, like the area, an iterator of its vertices, or the coordinate of its center, but this is good enough for our example. Now let's define a couple of implementation of this interface.
class Triangle(var a:Double, var b:Double, var c:Double) extends Polygon{ def this(x:Point, y:Point, z:Point) = this(x.distanceTo(y), y.distanceTo(z), z.distanceTo(x)) override def numSides = 3 override def perimeter = a + b + c } class Quadrilateral(var a:Double, var b:Double, var c:Double, var d:Double) extends Polygon{ def this(x:Point, y:Point, z:Point, w:Point) = this(x.distanceTo(y), y.distanceTo(z), z.distanceTo(w), w.distanceTo(x)) override def numSides = 4 override def perimeter = a + b + c + d }
Now we have two types of Polygons, a Triangle and a Quadrilateral. Notice that both of these have their own internal implementation based on the length of each of their sides. They both provide a constructor that uses Points as well though. Now we can define a factory for Polygons.
class PolygonFactory{ def createPolygon(v:List[Point]):Polygon = { var p:Polygon = null if (v.size == 3){ p = new Triangle(v(0), v(1), v(2)) } else if (v.size == 4){ p = new Quadrilateral(v(0), v(1), v(2), v(3)) } p } }
Our factory method takes a List of Points and returns some type of Polygon. If we have three vertices, then it invokes the constructor for a Triangle. If there are four vertices, it invokes the constructor for a Quadrilateral. This is the essence of the pattern: an interface that defers the construction of our objects to subtypes. This is hidden from the user of the factory. This is completely hidden to the users of PolygonFactory
:
object App{ def main(args:Array[String]){ var points = List(new Point(0.0, 0.0), new Point(3.0, 0.0), new Point(0.0, 4.0)) var p = PolygonFactory.createPolygon(points) println("Number of sides = " + p.numSides) println("Perimeter = " + p.perimeter) points = List(new Point(0.0, 0.0), new Point(3.0, 0.0), new Point(3.0, 4.0), new Point(0.0, 4.0)) p = PolygonFactory.createPolygon(points) println("Number of sides = " + p.numSides) println("Perimeter = " + p.perimeter) } }
Here we have a simple command line application with a main method. It creates different lists of vertices and passes them to the PolygonFactory
. The factory method returns a Polygon, but the caller doesn't know or care what subtype is returned. Instead it only needs the methods defined on the Polygon interface and invokes them as needed. If you have done a lot of object-oriented programming, there is a good chance that you have seen or used this pattern.
Now let's look at how this would be done using functional programming.
Let's start by re-examining our types.
case class Point(x:Double, y:Double) case class Triangle(a:Double, b:Double, c:Double) case class Quadrilateral(a:Double, b:Double, c:Double, d:Double)
There is a lot to notice here. First we are using case classes. These are somewhat like structs in C. They are an example of algebraic data types. They can be though of as wrappers around simple data types and other algebraic data types. In our example, our Point is simply the x and y coordinates, and our Triangle and Quadrilateral are just the lengths of their sides. Our case classes have no methods, unlike traditional objects. With no methods, there is no Polygon interface at all. Instead we keep these as separate functions:
object Polygon{ import java.lang.Math.sqrt def dist(a:Point, b:Point) = { val dx = a.x - b.x val dy = a.y - b.y sqrt(dx * dx + dy * dy) } def numSides(p:Any) = p match { case t:Triangle => 3 case q:Quadrilateral => 4 case _ => 0 } def perimeter(p:Any) = p match { case t:Triangle => t.a + t.b + t.c case q:Quadrilateral => q.a + q.b + q.c + q.d case _ => 0 } }
Here we have defined a function dist for calculating the distance between two Points. We have also defined two functions for giving the number of sides or the perimeter of a polygon. These functions use pattern matching, another important concept in functional programming. Pattern matching syntax in Scala is similar to the switch/case statement found in most imperative languages. However we are comparing types, not just integers in our match statement. We match against the type of object, and then perform the appropriate calculation. Notice that in each match statement we have a default case (denoted with the underscore) as well. Using algebraic data types and pattern matching together is a very common and very powerful pattern in functional programming. We still haven't addressed how to implement a factory method, but you might be able to make a good guess at this point:
object Polygon{ def createPolygon(xs:List[Point]) = xs match { case x::y::z::Nil => Triangle(dist(x,y), dist(y,z), dist(z,x)) case x::y::z::w::Nil => Quadrilateral(dist(x,y), dist(y,z), dist(z,w), dist(w,x)) case _ => xs } }
Now let's take a look at how we would use this more functional version of a factory method.
object App{ def main(args:Array[String]){ import Polygon._ val t = createPolygon(Point(0.0,0.0) :: Point(3.0, 0.0) :: Point(0.0, 4.0) :: Nil) println("Number of sides = " + numSides(t)) println("Perimeter = " + perimeter(t)) val q = createPolygon(Point(0.0,0.0) :: Point(3.0, 0.0) :: Point(3.0, 4.0) :: Point(0.0, 4.0) :: Nil) println("Number of sides = " + numSides(q)) println("Perimeter = " + perimeter(q)) } }
The usage is very similar to the object-oriented version. Our factory allows us to delegate the creation of the Triangle and Quadrilateral. We are then able to pass the result of the factory method to the numSides and perimeter functions. Obviously the usage here is slightly different, but it allow a similar pattern to the classical OOP variation. The key things to take away from this are algebraic data types and pattern matching. Many creational patterns can be expressed using these two key features of functional programming languages.
Now let's look a more nuanced pattern that is very popular in OOP, the Singleton Pattern.
The Singleton Pattern is one of the most beloved patterns in object-oriented programming. Countless developers have had to implement a singleton in pseudocode on a white-board as part of their interview process. At the same time, the pattern is also one of the most despised patterns in OOP. It essentially creates global state and global functions on that state. This is not only arguably anti-OOP, but leads to many practical headaches. Systems with singletons are infamously difficult to test. The technique of dependency injection is often touted as a way to have a testable system with singletons. It suffices to say that developers have a love-hate relationship with this pattern. So let's take a look at a singleton implementation in Scala.
The Singleton Pattern is one of the most beloved and most despised patterns in OOP.
class SysConfig private(val path:String){ private var configValues = new HashMap[String,String] Source.fromFile(path).getLines().foreach(line => { var index = line.indexOf("=") var key = line.slice(0,index) var value = line.takeRight(line.length - index - 1) configValues.put(key,value) }) def getValue(key:String) = configValues(key) }
In our example we have a singleton called SysConfig
. It reads data from a configuration file and the stores it in a hash-map. The private modifier before the parameter list in the class declaration indicates that the class has a private constructor that takes a string called path. The next several lines of code are part of this constructor. We use some Scala convenience APIs to read the file, break it apart line by line, and pass each line to an inline function (a closure). This function breaks each line apart at the equals sign, creating name-value pairs that are added to the hash-map. Finally our class declares a single method, getValue
that returns the value associated to a key from the configuration file. Now since our class only has a private constructor, we need some way to access it and complete the Singleton Pattern:
object SysConfig{ private val instance = new SysConfig("sys.config") def getInstance = instance }
In many OOP languages like C++ or Java, we would use a private static instance of SysConfig
along with a static method to access it. Scala does not have statics, but using an object accomplishes the same thing. Our SysConfig object has a private instance of the SysConfig
class and a getInstance
method that provides a handle to the private instance. Now we can use our SysConfig
singleton in the typical way:
object App{ def main(args:Array[String]){ println(SysConfig.getInstance.getValue("foo")) } }
Here we simply use getInstance
to get to our single instance of SysConfig
, and then we can invoke its getValue
method to read configuration data from our file.
Now let's take a look at more functional version of this pattern.
object SysConfig{ private lazy val configValues = Source.fromFile("sys.config").getLines() .map{ line => line.split("=") } .map{ array => array(0)->array(1) } .foldLeft(new HashMap[String,String]){ (map, mapping) => map += mapping } def apply(key:String) = configValues(key) }
SysConfig
class into the our SysConfig
object.configValues
map, just as before. However we are creating it in a single expression and declaring a lazy value. This means that the expression that defines the configValues
value will not be evaluated until it is needed, i.e., not until it is accessed via our access method (more on that shortly.)getLines()
is replaced by an array.Tuple2[String,String]
. So now we have a sequence of string tuples.We transform this sequence into a map by using a fold. A fold works by taking an initial expression and then applying a folding function to the initial expression and the first element. The result is then passed to the folding function along with the second element, etc. until all elements are exhausted and the final result is returned. In this case, our initial expression in a new HashMap. Our folding function takes the HashMap and adds a tuple to it, i.e., adding a mapping of the first element of the tuple to the second element of the tuple. After transversing all of the elements in the sequence we will be left with a HashMap that contains a mapping for each name-value pair from our configuration file.
This may seem like a more complex way to create a HashMap, but it is simply a more functional way. We are specifying a series of transformations (functions) to apply to the input data to produce the desired output data structure. This type of specification really pays off because it allows us to declare the HashMap as a lazy val. The configValues
value is created once and cannot be reassigned once it is create (val is like a const expression in C++ or a final declaration in Java) and its creation is deferred until it is needed. Lazy expressions are very common in functional programming, but these obviously require an expression in the first place.
Finally you probably noticed that we changed the name of the method used for accessing the configuration data. We have given it the seemingly strange name of apply. This is a special name in Scala (and many other functional languages) that allows us to treat the SysConfig object as a function. So now our usage code is as follows:
object App{ def main(args:Array[String]){ println(SysConfig("foo")) } }
Notice how we do not use the apply method name at all, and simply provide the parameter list to the SysConfig
object directly. We are able to treat it exactly as a function, and this is the true translation of a singleton. The methods of our singleton class are now simply functions. There is no SysConfig
singleton, there is a SysConfig
function instead.
We have now looked at a pair of the classical creational design patterns and seen how they translate to functional programming. Along the way we have been introduced to several key concepts of functional programming: algebraic data types, pattern matching, lazy expressions/values, and function objects. We have only scratched the surface and yet just these concepts provide us with very different ways to attack common programming problems. Now let's take a look at some structural design patterns and how they exist in a functional world. We will see some of the same functional programming concepts we have encountered so far, but some new ones as well.
There has been error in communication with Booktype server. Not sure right now where is the problem.
You should refresh this page.