The null object design pattern

Most object-oriented languages have a way of specifying the nonexistence of some value. In Scala and Java, for example, this could be the null value that can be assigned to an object. Calling any method on an object that is null would result in a NullPointerException, hence developers should be careful and check whether there is such a possibility. These checks, however, could make the source code hard to follow and extend as developers should always be aware. This is where the null object design pattern is helpful. Its purpose is:

Note

To define an actual object that represents the null value and has neutral behavior.

Using null objects removes the need to check whether something is set to null or not. The code becomes much more readable and easy to understand and makes bug occurrence harder.

Class diagram

For the class diagram, let's imagine that we have a system that has to poll a queue for messages. Of course, this queue might not always have anything to offer, so it would return nulls. Instead of checking for null, we could simply return special null objects that will have empty behavior. Let's show these message classes in a diagram:

Class diagram

Using the classes from the preceding diagram, whenever there is no number to print, we will return a NullMessage object with empty behavior. In some cases, for optimization purposes, people could have the NullMessage as a singleton instance.

Code example

Before we actually look at our code, we will note a few observations about the preceding diagram. It represents a classical case using the null object design pattern. However, nowadays, it is not really used this way in either Java or Scala. For example, Java now supports Optional, which is used instead (assuming people use new versions of the language). In Scala, things are similar—we can and do use Option[Message] instead of null objects. Additionally, we gain all the other nice features of Option such as the ability to use them in pattern matching.

So, as mentioned previously, our code is not really going to use the hierarchy of the preceding class diagram. It just doesn't need it and it will be much simpler. Instead, we will be using Option[Message]. First of all, let's see the Message class definition:

case class Message(number: Int) {
  def print(): String = s"This is a message with number: $number."
}

We mentioned that we will be polling a queue for messages and then displaying them. We have simulated a queue that is being randomly populated using a different thread in our application:

class DataGenerator extends Runnable {

  val MAX_VAL = 10
  val MAX_TIME = 10000
  
  private var isStop = false
  
  private val queue: ConcurrentLinkedQueue[Int] = new ConcurrentLinkedQueue[Int]()
  
  override def run(): Unit = {
    val random = new Random()
    while (!isStop) {
      Thread.sleep(random.nextInt(MAX_TIME))
      queue.add(random.nextInt(MAX_VAL))
    }
  }
  
  def getMessage(): Option[Message] =
    Option(queue.poll()).map {
      case number => Message(number)
    }

  def requestStop(): Unit = this.synchronized {
    isStop = true
  }
}

The preceding code shows what will be run in a different thread. The queue will be populated with random values between 0 (inclusive) and 10 (exclusive) at random intervals. Then getMessage can be called and whatever is in the queue can be read. Since it is possible for the queue to be empty, we return an Option of Message to the caller. It is probably worth mentioning that in Scala, Option(null) returns None. This is exactly what we took advantage of in the preceding code.

Let's see how everything comes together in our example:

object MessageExample {
  val TIMES_TO_TRY = 10
  val MAX_TIME = 5000
  
  def main(args: Array[String]): Unit = {
    val generator = new DataGenerator
    // start the generator in another thread
    new Thread(generator).start()
    
    val random = new Random()
    (0 to TIMES_TO_TRY).foreach {
      case time =>
        Thread.sleep(random.nextInt(MAX_TIME))
        System.out.println("Getting next message...")
        generator.getMessage().foreach(m => System.out.println(m.print()))
    }
    
    generator.requestStop()
  }
}

The preceding program creates a generator and makes it run on a different thread. Then it randomly requests items from the generator and prints them if something is actually returned. Due to the use of random generators, the program will print different things every time. Here is an example run:

Code example

As you can see from our example and the preceding output, we never actually check for nulls and our code just doesn't do anything when the queue returns null. This works really nicely in large projects and it makes the source code look really elegant and easy to understand.

Just for completeness, in real-life applications, code as in the previous example might not be a good idea. First of all, instead of calling sleep on a thread, we can use timers and second, and if we want to create producer-consumer applications, we can use libraries such as Akka (http://akka.io/), which allow us to do reactive programming and have really optimal code.

What is it good for?

As you already saw, the null object design pattern is already incorporated in Scala (and newer versions of Java) through the use of Option (Optional in Java). This makes it really easy to use and shows the power of the language once more. Using null objects makes our code look much more readable and removes the need to take extra care when a value is null. It also reduces the risk of bugs.

What is it not so good for?

There are no drawbacks of this design pattern that we can think of. Probably one thing worth mentioning is this—use it only when it is actually needed, not everywhere.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.50.156