Factory Objects in Scala Code

When programming with an object-oriented language like Scala it’s often necessary to create a different type of object, depending on the value of a configuration flag, the current state of the program, or other conditions. At this point your first idea may be to write something like:

val someFlag: Boolean = config("flag-name")
val realObject = if (someFlag) new Foo() else new Bar()

or even longer pieces of switching code, like:

val someFlag: String = config("flag-name")
val realObject: SomeClass = someFlag match {
  case "foo" => new Foo()
  case "bar" => new Bar()
  case _ => new Frob()
}

This is almost “ok” for one place, but it’s conflating two different things in one place: the fact that the type (and value) of realObject depends on the value of someFlag, and how the switching between different someFlag values works. What’s even worse, this sort of initialization for realObject binds the switching logic with this specific instance. If more copies of SomeClass are used elsewhere, the switching logic for someFlag will have to be repeated at all places. Then a few weeks or months later, you may decide to change something in the switching logic and miss updating one of those places, ending up with a nice bug on your plate.

Avoiding this sort of code duplication and writing the switching logic in a well-factored manner is exactly what factory objects can do.

I’ll use a trivial but easy to follow example to show how you can use factory objects in Scala. Let’s assume that you have two different classes in your code. One of them represents the home directory of a plain user, pointing at “/home/users/USERNAME“. The other one represents the home directory of a user with administrative rights, and it points at “/home/admins/USERNAME” instead. Your program also has some way to determine that it wants to create a ‘plain user’ or an ‘admin user’ home directory object, which can be useful at object creation time, like someFlag in the first example.

Since both types of objects will be “home directories”, it’s convenient if they are both sub-classes of a common, abstract superclass, e.g.:

import java.io.File

/*
 * Points at the home directory of {{userName}},
 * under a configurable {{baseDirectory}}.
 */
abstract class HomeDirectory(baseDirectory: String,
                             userName: String) {
  val path = new File(baseDirectory + "/" + userName)
}

class UserHomeDirectory(baseDirectory: String,
                        userName: String)
      extends HomeDirectory(baseDirectory, userName)

class AdministratorHomeDirectory(baseDirectory: String,
                                 userName: String)
      extends HomeDirectory(baseDirectory, userName)

Now it’s still tempting to write code like this:

def homeDirectory(userName: String,
  isAdministrator: Boolean): HomeDirectory = {
  if (isAdministrator)
    new AdministratorHomeDirectory("/home/admins", userName)
  else
    new UserHomeDirectory("/home/users", userName)
}

This is “ok” in the sense that it already works, and it does what we expect: a UserHomeDirectory object is created when isAdministrator is false, and an AdministratorHomeDirectory is created when we pass true:

scala> val h = homeDirectory("keramida", false)
h: HomeDirectory = UserHomeDirectory@400ae511

scala> h.path
res1: java.io.File = /home/users/keramida

scala> val h = homeDirectory("keramida", true)
h: HomeDirectory = AdministratorHomeDirectory@5b0a1d2d

scala> h.path
res2: java.io.File = /home/admins/keramida

Even so, we can still improve things a lot. Instead of having the switching logic inside a function that is completely isolated, and decoupled from the actual object classes, it would be nice if we could pass just the username and the flag to a HomeDirectory and have the decision about UserHomeDirectory vs. AdministratorHomeDirectory encoded in one, well-known and predictable place: the class itself. Then we would be able to write stuff like:

val home = HomeDirectory("keramida", isAdministrator = false)

or even just:

val home = HomeDirectory("keramida", false)

This is where Scala’s “companion objects” and factory methods really shine. A companion object in Scala is described like this in the Scala Reference Manual:

“Generally, a companion module of a class is an object which has the same name as the class and is defined in the same scope and compilation unit. Conversely, the class is called the companion class of the module.”

This doesn’t say a lot, but one of the interesting things that companion objects can do is to implement various apply() methods. These methods can be used to construct objects of the “companion class” (the class with the same name as the companion object), and they are very commonly used to implement factories like the one we want to implement.

Since we want to be able to create HomeDirectory subclass instances with the signature (username: String, isAdministrator: Boolean), this is what our apply() method should look like:

object HomeDirectory {
  def apply(userName: String, isAdministrator: Boolean) = {
    if (isAdministrator)
      new AdministratorHomeDirectory("/home/admins", userName)
    else
      new UserHomeDirectory("/home/users", userName)
  }
}

We can even move the base directory strings out of apply():

object HomeDirectory {
  val adminBase: String = "/home/admins"
  val userBase: String = "/home/users"

  def apply(userName: String, isAdministrator: Boolean) = {
    if (isAdministrator)
      new AdministratorHomeDirectory(adminBase, userName)
    else
      new UserHomeDirectory(userBase, userName)
  }
}

With that final piece in place, our source code is now a lot cleaner than the original, the switching between administrator vs. plain users does not have to be copied at every single place where we use a ‘home directory’ object:

import java.io.File

/* Abstract base class for all home directory types. */
abstract class HomeDirectory(baseDirectory: String,
                             userName: String) {
  val path = new File(baseDirectory + "/" + userName)
}

/* Home directory for a simple, non-administrator user. */
class UserHomeDirectory(baseDirectory: String,
                        userName: String)
    extends HomeDirectory(baseDirectory, userName)

/* Home directory for a user with administrator rights. */
class AdministratorHomeDirectory(baseDirectory: String,
                                 userName: String)
    extends HomeDirectory(baseDirectory, userName)

object HomeDirectory {
  val adminBase: String = "/home/admins"
  val userBase: String = "/home/users"

  /* Factory method for home directories. */
  def apply(userName: String, isAdministrator: Boolean) = {
    if (isAdministrator)
      new AdministratorHomeDirectory(adminBase, userName)
    else
      new UserHomeDirectory(userBase, userName)
  }
}

With all the bits in place, now we can do what we originally set out to do, in a much cleaner manner. Create different types of HomeDirectory subclasses, without all the messy ‘switching’ logic polluting every single call site. We can create a HomeDirectory object for a plain user, and have it automatically point to the right place under “/home/users”:

scala> val h = HomeDirectory("keramida", false)
h: HomeDirectory = UserHomeDirectory@615e10ab

scala> h.path
res0: java.io.File = /home/users/keramida

We can also create a HomeDirectory object for a user with administrative rights, pointing under “/home/admins”:

scala> val h = HomeDirectory("superuser", true)
h: HomeDirectory = AdministratorHomeDirectory@7490649e

scala> h.path
res1: java.io.File = /home/admins/superuser

The call is always the same: make me a HomeDirectory. It’s the actual instance class that changes, and what the instance itself contains.