The Beauty of the Null Object Pattern

Before we start...

Let me start by saying this: NEVER dismiss an idea without giving it any consideration. That was the mistake I made when I first encountered an article about this design pattern many years ago. I was thrown off by the name and without even attempting to find out what this design pattern was all about, I moved on to other things. That was a huge mistake and I am not afraid to admit it. I urge you not to make that mistake. And if you have already, never make that mistake again. Trust me, you will thank me later. For now, let me tell you my story and attempt to explain when to use it along with a simple example on how to implement this pattern.

The Null Object Pattern

By definition, the main purpose of this pattern is to have an Object-Oriented alternative to the absence of objects by creating objects that display "do nothing" behavior. That sounds like a mouthful. In simplest terms, it is meant to provide a mechanism to replace null references with actual objects instances that represent "nothingness." If you explore the origins of the number zero, you should be able to understand why having an object to represent "nothing" makes sense. The motivation behind this pattern is to allow applications to continue to work normally even if a null reference is encountered. It also makes your code much simpler because null objects should eliminate the need for conditionals to check whether an object is null prior to using it. For the most part, the diagram below illustrates the typical Null Object pattern. In the simplest of terms, a base class (or preferably an interface) with many sub-classes where one of these is the Null Object class. However, where many sources of this topic fall short, in my opinion, is to point out a very crucial characteristic the Null Object class must have: it MUST be an immutable class. It would be extremely bad if your null object, out of the sudden, mutates and it is not null anymore.
Typical UML diagram of Null Object Pattern
So, my three recommendations on how to design your null object class:
  1. Make your Null Object class final
  2. Don't have any fields in your Null Object class that are null themselves
  3. Override all getters and setters to "do nothing"
One slight change in my "typical" UML illustration is that my base class does not indicate being "abstract." Even though I already mentioned that you should preferably use an interface based on the coding principle to always code to abstractions, for this article I am trying to make the point that you can also apply this pattern to existing concrete classes.

Make your Null Object class final

The point behind making the Null Object class final is to prevent the existence of a sub type of the Null Object class to be not null. In other words, you cannot have a sub class to represent some thing when its super type represents "nothing." That goes against all logic. In Java, this is accomplished by using the final keyword in the class declaration:

public final class NullObject {
  // body of class omitted
}
My blog is dedicated to solutions using the Java language. That said, if you would like to implement a similar solution in your particular language of choice, I am certain that you could do so easily. For example, in C++ you can make the class constructor final and no friends of the class. Contact me if you would like me to help you design a solution using the language of your choice.

Don't have any fields in your Null Object class that are null themselves


public class SuperClass {

  private int x = 3;
  private String text = "Hello there!";

  public SuperClass() {...}
  public SuperClass(int x, String text) {...}

  // setters and getters omitted
}

public final class NullObject extends SuperClass {

  // setters and getters class omitted
}
Most likely, your base class will have many type of fields; hopefully all with private access. However, if the class contains protected fields, this could be a bit of a problem. If the base class contains non-private fields (public, protected, default), the best thing to do is to provide a no-argument constructor and initialize to the default (empty) values that you need for your application. Here's an example...

public class SuperClass {

  protected String text;

  // setters and getters class omitted
}

public final class NullObject extends SuperClass {

  public NullObject() {
    text = "null value"; // field in superclass is no longer null;
  }

  // setters and getters class omitted
}
This necessary so that

NullObject nullObj = new NullObject();
System.out.println(nullObject.text);
System.out.println(nullObject.getText());
both of the print calls display the same value. In this example, both calls will print out "null value" to the console. You might be asking yourself, why omit the explicit, no-argument constructor altogether? If your class contain object references, you do not want to return null references for each one of these. Remember, the point of NullObject pattern is to replace null references with null objects. For String objects, it is fine to return an empty string and avoid creating an explicit constructor. For other objects, this may not be advisable. It would be wasteful to return new instances of null objects in each "getter". Now you can see why providing a no-argument constructor that sets all these null references is a better solution. As your program runs, you do not want new null objects to keep getting allocated; not knowing when (if ever) they will be garbage collected. Lastly, for numeric values, it is up to you to decide what is a good value to return with your null object. Typically zero is a good value, but it has to be a value that makes sense for your particular requirements. For example, sometimes a negative value makes more sense than zero for a default value.

Override all getters and setters to do nothing


public class SuperClass {

  private int x = 3;
  protected String text = "Hello there!";

  public SuperClass() {...}
  public SuperClass(int x, int y, String text) {...}

  public int getX() {
    return x;
  }

  public String getText() {
    return text;
  }

  public void setX(int x) {
    this.x = x;
  }

  public void setText(int text) {
    this.text = text;
  }
}

public final class NullObject extends SuperClass {

  public NullObject() {
    text = "null value";
  }

  @Override
  public int getX() {
    return 0; // or some other value that might make more sense in a "null object"
  }

  @Override
  public String getText() {
    return text;
  }

  @Override
  public void setX(int x) {
    // do nothing
  }

  @Override
  public void setText(int text) {
    // do nothing
  }
}

My story ("real world" example)

About a year ago, while gathering information about Design Patterns to create training materials to train junior developer at my current job, I came across an article on Null Object pattern and that led me to Christopher Okhravi's video on the Null Object pattern. If you haven't seen any of his videos, I strongly recommended you watch them and subscribe. His delivery is simply amazing. Moving on, I was amaze that this "thing" I thought it was dumb and without merit, was in fact very powerful. So I decided to use it in my application. Without getting in the details of what my code does, just know that my application needs to consume different "models" in order to get certain configuration items that allow me to interact with different other applications. So, back then, I made the life-changing decision to give this pattern a try. My main motivation was that, if I was to query for a particular model name and the name did not exist, I should be able to continue operating without even having to check for null references.

currentModel = modelMap.entrySet().stream().filter(e -> key.equalsIgnoreCase(e.getKey()))
         .map(Map.Entry::getValue).findFirst().orElse(new NullModel());
The code snippet above is really the secret sauce. I know that it looks as I am not following my own advice (about creating new instances all the time). However, for this specific case, it makes sense since this application is not a service that it is always running. If you understand lambda expressions, please bear with me. For those of you who do not, this code snippet looks for a model in a map associated with a key value. It will return the first (and only) value with matching key or it returns a NullModel object instead of a null reference. As shown on the picture above, as well as the previous code examples, my NullModel class extended a base Model class. Internally, all object references return other types of null objects.

public final class NullModel extends Model {

  @Override
  public List getModelElements() {
    List elements = new ArrayList<>();
    elements.add(new NullElement());
    return new UnmodifiableList<>(elements); // to guarantee immutability
  }

  @Override
  public void setModelElements(List elements) {
    // Do nothing
  }
}
The ModelElement class simply contains data elements for the different configuration items for the application in question. They are just String values. Using the code snippet that returns the current model, I could invoke the method that returns the current model and simply use it without fearing that my application could break; even when passing an invalid key value. So, for example

currentModel = switchModel("validKey");
System.out.println("Model name: " + currentModel.getName()); // Displays a real model name

currentModel = switchModel("invalidKey");
System.out.println("Model name: " + currentModel.getName()); // Displays "N/A" instead of null
In fact, not only the value returned from the "getName()" method doesn't result in displaying null, it also doesn't result in a NPE by calling a method in a null reference; since 'currentModel' is a tangible object reference that represents absence of a model. This underlined part is key for how this story ends. Fast forward until about a month ago. A long time since I implemented the use of Null Object pattern into my application. I was asked to use this "model parser" with a legacy application that does not have configuration "models" like our new applications. It took me seconds to realize this is not just doable, but EXTREMELY EASY to do since I could leverage my implementation of the Null Object pattern in my "model parser" to determine that, when interacting with our legacy application, if my model parser encounters a "null model", it would simply fall back to obtain the configuration items the old fashion way, AND if the legacy application even makes use of this model approach to store its configuration, the code in place will simply work because instead of returning a "null model", it will return a valid model for the legacy application. No code change will ever be needed. The story ends this way: the integration of the legacy model (null model) parsing was done by another member of the team who wasn't familiarized with what I created, and he was able to integrate this in basically one day.

Final words

The simplicity and power of this design pattern blew my mind once I decided to give it serious thought and consideration. And, it blew my mind even more when I saw pay dividends almost a year later. I hope you enjoyed reading this as much as I enjoyed writing it for you. I would like it very much for you to share your thoughts on the comments section. I would also love for you to subscribe to my blog and to my YouTube channel. I am planning to add more contents on a regular basis. Thank you, and I will see you around.

Here's a link to my video example on YouTube https://youtu.be/cqVqOpdPMHA

Comments

Popular posts from this blog

Implementing Interfaces with Java Records

Customizing Java Records

Exception Handling: File CRUD Operations Example