1. Overview

In this quick tutorial, we’ll have a look at the Null Object Pattern, a special case of the Strategy Pattern. We’ll describe its purpose and when we should actually consider using it.

As usual, we’ll also provide a simple code example.

2. Null Object Pattern

In most object-oriented programming languages, we’re not allowed to use a null reference. That’s why we’re often forced to write null checks:

Command cmd = getCommand();
if (cmd != null) {
    cmd.execute();
}

Sometimes, if the number of such if statements get high, the code may become ugly, hard to read and error-prone. This is when the Null Object Pattern may come in handy.

The intent of the Null Object Pattern is to minimize that kind of null check. Instead, we can identify the null behavior and encapsulate it in the type expected by the client code. More often then not, such neutral logic is very simple – do nothing. This way we no longer need to deal with special handling of null references.

We simply may treat null objects the same way we treat any other instance of a given type that actually contains some more sophisticated business logic. Consequently, the client code stays cleaner.

As null objects should not have any state, there’s no need to create identical instances multiple times. Thus, we’ll often implement null objects as singletons.

3. Null Object Pattern’s UML Diagram

Let’s look at the pattern visually:

NOP

As we can see, we can identify the following participants:

  • Client requires an instance of AbstractObject
  • AbstractObject defines the contract Client expects – it may also contain shared logic for the implementing classes
  • RealObject implements AbstractObject and provides real behavior
  • NullObject implements AbstractObject and provides neutral behavior

4. Implementation

Now that we have a clear idea of the theory, let’s look at an example.

Imagine we have a message router application. Each message should have a valid priority assigned. Our system is supposed to route high priority messages to an SMS gateway whereas messages with medium priority should be routed to a JMS queue.

From time to time, however, messages with “undefined” or empty priority might come to our application. Such messages should be discarded from further processing.

First, we’ll create the Router interface:

public interface Router {
    void route(Message msg);
}

Next, let’s create two implementations of the above interface – the one responsible for routing to an SMS gateway and the one that will route the messages to JMS queue:

public class SmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}
public class JmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}

Finally, let’s implement our null object:

public class NullRouter implements Router {
    @Override
    public void route(Message msg) {
        // do nothing
    }
}

We’re now ready to put all the pieces together. Let’s see how the example client code may look like:

public class RoutingHandler {
    public void handle(Iterable<Message> messages) {
        for (Message msg : messages) {
            Router router = RouterFactory.getRouterForMessage(msg);
            router.route(msg);
        }
    }
}

As we can see, we treat all Router objects the same way, no matter what implementation is returned by the RouterFactory. This allows us to keep our code clean and readable.

5. When to Use the Null Object Pattern

We should use the Null Object Pattern when a Client would otherwise check for null just to skip execution or perform a default action. In such cases, we may encapsulate the neutral logic within a null object and return that to the client instead of the null value. This way client’s code no longer needs to be aware if a given instance is null or not.

Such an approach follows general object-oriented principles, like Tell-Don’t-Ask.

To better understand when we should use the Null Object Pattern, let’s imagine we have to implement CustomerDao interface defined as follows:

public interface CustomerDao {
    Collection<Customer> findByNameAndLastname(String name, String lastname);
    Customer getById(Long id);
}

Most of the developers would return Collections.emptyList() from findByNameAndLastname() in case none of the customers matches the provided search criteria. This is a very good example of following the Null Object Pattern.

In contrast, the get**ById() should return the customer with the given id. Someone calling this method expects to get the specific customer entity. In case no such customer exists we should explicitly return null to signal there is something wrong with the provided id. 

As with all other patterns, we need to consider our specific use case before blindly implementing the Null Object Pattern. Otherwise, we may unintentionally introduce some bugs in our code that will be hard to find.

6. Conclusion

In this article, we learned what the Null Object Pattern is and when we may use it. We also implemented a simple example of the design pattern.

As usual, all the code samples are available over on GitHub.