Skip to end of metadata
Go to start of metadata

The Null Object Pattern involves using a special object place-marker object representing null. Typically, if you have a reference to null, you can't invoke reference.field or reference.method(). You receive the dreaded NullPointerException. The null object pattern uses a special object representing null, instead of using an actual null. This allows you to invoke field and method references on the null object. The result of using the null object should semantically be equivalent to doing nothing.

Simple Example

Suppose we have the following system:

When run, this prints out 1200. Suppose now that we now invoke:

If we now try to calculate biggestSalary again, we receive a null pointer exception.

To overcome this problem, we can introduce a NullJob class and change the above statement to become:

This works as we require but it's not always the best way to do this with Groovy. Groovy's safe-dereference operator (?.) operator and null aware closures often allow Groovy to avoid the need to create a special null object or null class. This is illustrated by examining a groovier way to write the above example:

Two things are going on here to allow this to work. First of all, max() is 'null aware' so that [300, null, 400].max() == 400. Secondly, with the ?. operator, an expression like p?.job?.salary will be equal to null if salary is equal to null, or if job is equal to null or if p is equal to null. You don't need to code a complex nested if ... then ... else to avoid a NullPointerException.

Tree Example

Consider the following example (inspired by this) where we want to calculate size, cumulative sum and cumulative product of all the values in a tree structure.

Our first attempt has special logic within the calculation methods to handle null values.

If we introduce the null object pattern (here by defining the NullTree class), we can now simplify the logic in the size(), sum() and product() methods. These methods now much more clearly represent the logic for the normal (and now universal) case. Each of the methods within NullTree returns a value which represents doing nothing.

The result of running either of these examples is:

Note: a slight variation with the null object pattern is to combine it with the singleton pattern. So, we wouldn't write new NullTree() wherever we needed a null object as shown above. Instead we would have a single null object instance which we would place within our data structures as needed.

  • No labels