blob: ee9c12d78e980b4c6767e6ae1788d203c8889933 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Null Object Pattern
The http://en.wikipedia.org/wiki/Null_Object_pattern[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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_simple_example,indent=0]
----
When run, this prints out `1200`. Suppose now that we now invoke:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_simple_example2,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_simple_example3,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_simple_example4,indent=0]
----
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 ` 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 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.
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_tree_example,indent=0]
----
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.
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=null_object_tree_example2,indent=0]
----
The result of running either of these examples is:
----
4
14
120
----
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.