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.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.
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 hear to allow this to work. First of all functions like
max() are 'null aware' so that
300, null, 400.max() == 400 and 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
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
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.