Null Object Design Pattern

Null Object Design Pattern

Nulls are bad

I call it my billion-dollar mistake…At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. – Tony Hoare, inventor of ALGOL W.

But why is null so problematic?

One reason is that it indicates a distinct state that any type can have. Instead of defining a new type for each state an entity needs to express, we confuse our abstraction with this generalized value. Additionally, it’s difficult to reason about null when debugging and finding null-related bugs requires determining the program’s state and what led to it.

When null references are used as valid states for parameters and return values, we have to duplicate null reference checks in various places. This is where the Null Object Pattern comes in. It addresses the root cause of the troubles null introduces by using a specialized value to represent a distinct state instead of a generalized value.

Introducing the Null Object Pattern: A Solution for Taming Null References

The fundamental issue with null references is that they use a generalized value to represent a distinct state. This can lead to confusion and errors in the code. One solution to this problem is the use of the Null Object Pattern. While it may require more effort to create a NullObject class for each interface with which you want to represent a special state, the benefits are significant. The specialization is isolated to each special state, allowing for more precise and clear code. Instead of having scattered if statements checking for null references throughout the code, the Null Object Pattern provides a more organized and maintainable approach.

Summary:

  • The root cause of issues with null references is their use of a generalized value to represent a distinct state.

  • The Null Object Pattern is a solution that requires creating a NullObject class for each interface representing a special state.

  • This approach isolates specialization to each special state and results in more precise and clear code.

  • The Null Object Pattern provides a more organized and maintainable approach than scattered if statements checking for null references.

Implementing the Null Object Pattern

Let’s say we have an interface Animal with a method makeSound():

public interface Animal {
    void makeSound();
}

We can have different classes that implement this interface, such as Dog and Cat:

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

Now, let’s say we have a method that takes an Animal as a parameter and calls its makeSound() method. Without the Null Object Pattern, we would have to check for null references before calling the method:

public void doSomething(Animal animal) {
    if (animal != null) {
        animal.makeSound();
    }
}

With the Null Object Pattern, we can create a NullAnimal class that implements the Animal interface and provides a default implementation for the makeSound() method:

public class NullAnimal implements Animal {
    @Override
    public void makeSound() {
        // do nothing
    }
}

Now, instead of checking for null references, we can simply pass an instance of NullAnimal to the doSomething() method if we don’t have a valid Animal object:

public void doSomething(Animal animal) {
    animal.makeSound();
}

doSomething(new Dog()); // prints "Woof!"
doSomething(new Cat()); // prints "Meow!"
doSomething(new NullAnimal()); // does nothing

This approach eliminates the need for null reference checks and makes the code more organized and maintainable.