scala-logo-small

Factory Method Pattern in Scala

Continuing on with my series of design patterns in Scala, here’s an example of the Factory Method pattern. Since this pattern is as OO as you can get, there’s not as major of a difference in approach from languages like Java as there was with some of the other patterns. But as you hopefully have learned to expect, Scala has a few innovations that make the pattern more elegant! These include sealed traits, apply methods, and pattern matching.


Simplest Example

Many examples of factory methods seem to ignore why the pattern makes sense. If it makes it harder to create a new object than the standard way of calling “new SomeObject(…)” then what is the point of the pattern? For example at times when you could easily write “new Dog()” I have seen many examples of the factory method pattern where you have to instead use “Animal.create(“dog”)” or “Animal.create(Dog.class)”. To me this is nonsense. If I know I want a new Dog, I’ll just say new Dog(). So I think a better example is in order — an example where the client code doesn’t know or shouldn’t need to know what subclass will be created.

Here is one such example. Let’s say I have two types: Route and Vehicle. Only specific route and vehicle pairings make sense. You don’t want to take a moped on train tracks or a train on the street!

The client has the Route, and wants a Vehicle for it, but doesn’t need to be hassled with the details of what Vehicle to create and how to create it. The factory method design pattern can encapsulate the relationship between Route and Vehicle and create a place in the code with the single responsibility of creating new vehicles, based on the route that needs to be traveled. Of course the most sensible place for this is in Vehicle itself. I can pass a route to the Vehicle class and expect that a suitable Vehicle instance will be returned.

A similar example is given in the GoF Design Patterns book on page 109 starting under bullet point 2 “Connects parallel class hierarchies.”

Here’s the Code

Walkthrough
Lines 1 – 4:

  sealed abstract class Vehicle(val go: String)
  class HotRod extends Vehicle("Vrooooom!")
  class Train extends Vehicle("Chooo choooo")
  class Moped extends Vehicle("Bzzzzzzzzzzz")

We create three vehicles extending a sealed abstract class called “Vehicle.”

Lines 6 – 9:

  sealed abstract class Route(val name: String)
  class Highway extends Route("Interstate")
  class CityStreet extends Route("Boulevard")
  class TrainTrack extends Route("Rail")

Similarly to lines 1 – 4, we create different types of routes, extending a sealed abstract class called “Route.”

(More on what sealed means in a minute.)

If you are new to Scala, this example shows how Scala can create multiple separate classes in one file, something that is verboten in Java. Since Scala classes can be simple one-liners like this, there’s no reason not to allow this.

Now, about sealed traits and abstract classes. The sealed keyword is an amazing feature of Scala that you can read more about in Underscore’s great post Everything You Need to Know About Sealed Traits. For this example all you need to know is that if you use “sealed” then you must create all subtypes in the same file, and doing so will give you compile-time exhaustiveness checking. What that means is that any time we do a pattern match against Vehicles or Routes (*cough* lines 13-17 *cough*), if we forget to include one as a case in the pattern match, the compiler will warn us. The warning looks like:

SendVehicles.scala:13: warning: match may not be exhaustive.
It would fail on the following input: TrainTrack()
r match {
^
one warning found

Continuing on with the walkthrough…

Line 11, 12:

 object Vehicle {
  def apply(r: Route): Vehicle = {

“Object Vehicle” is a Companion Object of the Vehicle class. Normally the “Object” keyword defines a singleton. In this case, though, the “Object” has the same name as a class or trait, so it is a “Companion Object.” I have already covered this in the Static Factory Methods in Scala post (different but similar to what we are describing here) but the simple way I have thought about it since I was mostly a Java dev prior to coming to Scala, is that this is where “static” members of a class or trait live.

What is more important, is that in line 12 we define an apply method. When a trait or class in Scala has a companion object with an apply method, then that class or trait can be called without a new keyword, and the apply method will be invoked. If you look at lines 26-28, you see syntax like “Vehicle(interstate5)” rather than “new Vehicle(interstate5)” — the apply method is at work here. Apply methods are meant to be Scala’s version of factory methods built into the language. You should read the little section about Listing 3.2 in the book Programming in Scala. One benefit of this apply factory method pattern is the way it removes common boilerplate code that otherwise distracts from the meaning and intention of the code.

Lines 13-17:

  r match {
      case _: Highway => new HotRod
      case _: CityStreet => new Moped
      case _: TrainTrack => new Train
    }

Specifically demonstrates pattern matching by type. This is the preferred way to do something based on type. It is preferred over using instanceof because as I mentioned above, pattern matching against sealed traits or abstract classes can give you exhaustiveness checking.

As regards the factory method pattern, these features taken together (sealed, apply, and the companion object) demonstrate how a superclass (Vehicle in this example) can encapsulates the logic necessary to determine which of its subclasses to create. This is a key piece of the pattern.

The final lines, 21-29 are just a little test program which should be obvious. The output of this program is:

For the Interstate our vehicle sounds like Vrooooom!
For the Boulevard our vehicle sounds like Bzzzzzzzzzzz
For the Rail our vehicle sounds like Chooo choooo

You can download SendVehicles.scala from Github Gists and compile and run it with your local copy of Scala.

Leave a Reply

Your email address will not be published. Required fields are marked *