© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_25

25. Null Object Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

Wikipedia says, “In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). It was first published in the Pattern Languages of Program Design book series.” The Hillside Group sponsors Pattern Languages of Programs (PLoP) annual conferences.

The pattern can implement a “do-nothing” relationship or it can provide a default behavior when an application encounter with a null object instead of a real object. In simple words, the core aim is to make a better solution by avoiding “null objects check” or “null collaborations check” through if blocks. Using this pattern, you try to encapsulate the absence of an object by providing a default behavior that does nothing.

Concept

The notable characteristic of this pattern is that you do not need to do anything (or store nothing) when you invoke an operation on a null object. Consider the following program and the corresponding output. Let’s try to understand the problems associated with the following program segment, analyze the probable solutions and at the end of this chapter, you see a better implementation that uses this design pattern.

In the following implementation, let’s assume that you have two types of vehicles: bus and train. A client can opt for a bus or a train object through different input, like “a” or “b”. Let’s further assume that the application considers these two as the valid input only.

A Faulty Program

Here is a faulty program.
package jdp2e.nullobject.context.demo;
import java.util.Scanner;
interface Vehicle
{
    void travel();
}
class Bus implements Vehicle
{
    public static int busCount = 0;
    public Bus()
    {
        busCount++;
    }
    @Override
    public void travel()
    {
        System.out.println("Let us travel with a bus");
    }
}
class Train implements Vehicle
{
    public static int trainCount = 0;
    public Train()
    {
        trainCount++;
    }
    @Override
    public void travel()
    {
        System.out.println("Let us travel with a train");
    }
}
public class NeedForNullObjectPattern {
    public static void main(String[] args) {
        System.out.println("***Need for Null Object Pattern Demo*** ");
        String input = null;
        int totalObjects = 0;
        while (true)
        {
            System.out.println("Enter your choice( Type 'a' for Bus, 'b' for Train ) ");
            Scanner scanner=new Scanner(System.in);
            input = scanner.nextLine();
            Vehicle vehicle = null;
            switch (input.toLowerCase())
            {
            case "a":
                vehicle = new Bus();
                break;
            case "b":
                vehicle = new Train();
                break;
            }
            totalObjects = Bus.busCount + Train.trainCount;
            vehicle.travel();
            System.out.println("Total number of objects created in the system is : "+ totalObjects);
        }
    }
}

Output with Valid Inputs

***Need for Null Object Pattern Demo***
Enter your choice( Type 'a' for Bus, 'b' for Train )
a
Let us travel with a bus
Total number of objects created in the system is : 1
Enter your choice( Type 'a' for Bus, 'b' for Train )
b
Let us travel with a train
Total number of objects created in the system is : 2
Enter your choice( Type 'a' for Bus, 'b' for Train )
b
Let us travel with a train
Total number of objects created in the system is : 3
Enter your choice( Type 'a' for Bus, 'b' for Train )

Analysis with an Unwanted Input

Let’s assume that by mistake, the user has supplied a different character ‘d’ now as shown below:
***Need for Null Object Pattern Demo***
Enter your choice( Type 'a' for Bus, 'b' for Train )
a
Let us travel with a bus
Total number of objects created in the system is : 1
Enter your choice( Type 'a' for Bus, 'b' for Train )
b
Let us travel with a train
Total number of objects created in the system is : 2
Enter your choice( Type 'a' for Bus, 'b' for Train )
b
Let us travel with a train
Total number of objects created in the system is : 3
Enter your choice( Type 'a' for Bus, 'b' for Train )
d

Encountered Exception

This time, you receive the System.NullPointerException runtime exception.
Enter your choice( Type 'a' for Bus, 'b' for Train )
d
Exception in thread "main" java.lang.NullPointerException
    at jdp2e.nullobject.context.demo.NeedForNullObjectPattern.main(NeedForNullObjectPattern.java:61)

Immediate Remedy

The immediate remedy that may come in your mind is to do a null check before you invoke the operation as follows:
//A immediate remedy
if(vehicle !=null)
{
    vehicle.travel();
}

Analysis

The prior solution works in this case. But think of an enterprise application. If you need to do null checks for each possible scenario, you may need to have a larger number of if conditions to evaluate each time you proceed, and this approach makes your code dirty. At the same time, you may notice the side effects of a difficult maintenance also. The concept of null object pattern is useful in similar cases.

Real-World Example

Let’s consider a real-life scenario with a washing machine. A washing machine can wash properly if the door is closed and there is a smooth water supply without any internal leakage. But suppose, in one occasion, you forget to close the door or stopped the water supply in between. The washing machine should not damage itself in those situations. It can beep some alarm to draw your attention and indicate that there is no water at present or the door is still open.

Computer-World Example

Assume that in a client server architecture, the server does some kinds of processing based on the client input. The server should be intelligent enough, so that it does not initiate any calculation unnecessarily. Prior processing the input, it may want to do a cross verification to ensure whether it needs to start the process at all or it should ignore an invalid input. You may notice the use of the command pattern with a null object pattern in such a case.

Basically, in an enterprise application, you can avoid a large number of null checks and if/else blocks using this design pattern. The following implementation can give you a nice overview about this pattern.

Note

In Java, you may have seen the use of various adapter classes in java.awt.event package. These classes can be thought closer to null object pattern. For example, consider the MouseMotionAdapter class. It is an abstract class but contains methods with empty bodies like mouseDragged(MouseEvent e){ }, mouseMoved(MouseEvent e){ }. But since the adapter class is tagged with abstract keyword, you cannot directly create objects of the class.

Illustration

As before, in the following implementation, let’s assume that you have two types of vehicles: bus and train. A client can opt for a bus or a train through different input: “a” or “b”. If by mistake, the user supplies any invalid data (i.e., any input other than “a” or “b” in this case), he cannot travel at all. The application ignores an invalid input by doing nothing using a NullVehicle object . In the following example, I’ll not create these NullVehicle objects repeatedly. Once it is created, I’ll simply reuse that object.

Class Diagram

Figure 25-1 shows the class diagram. (The concept is implemented with a singleton pattern, so that, you can avoid unnecessary object creations).
../images/395506_2_En_25_Chapter/395506_2_En_25_Fig1_HTML.jpg
Figure 25-1

Class diagram

Package Explorer View

Figure 25-2 shows the high-level structure of the program.
../images/395506_2_En_25_Chapter/395506_2_En_25_Fig2_HTML.jpg
Figure 25-2

Package Explorer view

Implementation

Here’s the implementation.
package jdp2e.nullobject.demo;
import java.util.Scanner;
interface Vehicle
{
    void travel();
}
class Bus implements Vehicle
{
    public static int busCount = 0;
    public Bus()
    {
        busCount++;
    }
    @Override
    public void travel()
    {
        System.out.println("Let us travel with a bus");
    }
}
class Train implements Vehicle
{
    public static int trainCount = 0;
    public Train()
    {
        trainCount++;
    }
    @Override
    public void travel()
    {
        System.out.println("Let us travel with a train");
    }
}
class NullVehicle implements Vehicle
{
    //Early initialization
    private static  NullVehicle instance = new NullVehicle();
    public static int nullVehicleCount;
    //Making constructor private to prevent the use of "new"
    private NullVehicle()
    {
        nullVehicleCount++;
        System.out.println(" A null vehicle object created.Currently null vehicle count is :  "+nullVehicleCount);
    }
    // Global point of access.
    public static NullVehicle getInstance()
    {
        //System.out.println("We already have an instance now. Use it.");
        return instance;
    }
    @Override
    public void travel()
    {
        //Do Nothing
    }
}
public class NullObjectPatternExample  {
    public static void main(String[] args) {
        System.out.println("***Null Object Pattern Demo*** ");
        String input = "dummyInput";
        int totalObjects = 0;
        Scanner scanner;
        while(!input.toLowerCase().contains("exit"))
        {
            System.out.println("Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. ) ");
            scanner=new Scanner(System.in);
            if(scanner.hasNextLine())
            {
                input = scanner.nextLine();
            }
            Vehicle vehicle = null;
            switch (input.toLowerCase())
            {
            case "a":
                vehicle = new Bus();
                break;
            case "b":
                vehicle = new Train();
                break;
            case "exit":
                System.out.println("Closing the application");
                vehicle = NullVehicle.getInstance();
                break;
            default:
                System.out.println("Invalid input");
                vehicle =  NullVehicle.getInstance();
            }
            totalObjects = Bus.busCount + Train.trainCount+NullVehicle.nullVehicleCount;
            //A immediate remedy
            //if(vehicle !=null)
            //{
            vehicle.travel();
            //}
            System.out.println("Total number of objects created in the system is : "+ totalObjects);
        }
    }
}

Output

Here’s the output.
***Null Object Pattern Demo***
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. )
a
 A null vehicle object created.Currently null vehicle count is :  1
Let us travel with a bus
Total number of objects created in the system is : 2
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. )
b
Let us travel with a train
Total number of objects created in the system is : 3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. )
c
Invalid input
Total number of objects created in the system is : 3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. )
dfh
Invalid input
Total number of objects created in the system is : 3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to close the application. )
exit
Closing the application
Total number of objects created in the system is : 3

Analysis

  • Invalid input and their effects are shown in bold.

  • Apart from the initial case, notice that object count has not increased due to null vehicle objects or invalid input.

  • I did not perform any null check this time (notice the commented line in the following segment of code).

    //A immediate remedy
      //if(vehicle !=null)
      //{
       vehicle.travel();
      //}
  • This time program execution is not interrupted due to the invalid user input.

Q&A Session

  1. 1.

    At the beginning, I see that an additional object is created. Is it intentional?

    To save memory, I followed a singleton design pattern mechanism that supports early initialization in the structure of the NullVehicle class. I do not want to create a NullVehicle object for each invalid input. It is very likely that the application may need to deal with a larger number of invalid input. If you do not guard this situation, a large number of NullVehicle objects reside in the system (which are basically useless) and those occupy more memory. As a result, you may notice some typical side effects (for example, the system becomes slow, etc.).

     
  2. 2.

    To implement a simple null object pattern, I can ignore different object counters(that used in the prior example) and reduce lots of code. Is this correct?

    Yes. Ideally, consider the following code segment.
    //Another context
    List<Vehicle> vehicleList=new ArrayList<Vehicle>();
    vehicleList.add(new Bus());
    vehicleList.add(new Train());
    vehicleList.add(null);
    for( Vehicle vehicle : vehicleList)
      {
          vehicle.travel();
      }

    You cannot loop through this code because you encounter the java.lang.NullPointerException.

    Note a class like the following.
    class NullVehicle implements Vehicle
    {
        @Override
        public void travel()
        {
            //Do nothing
        }
    }
    And you code like this:
    //Another context discussed in Q&A session
    List<Vehicle> vehicleList=new ArrayList<Vehicle>();
    vehicleList.add(new Bus());
    vehicleList.add(new Train());
    //vehicleList.add(null);
    vehicleList.add(new NullVehicle());
    for( Vehicle vehicle : vehicleList)
    {
     vehicle.travel();
    }
    This time you can loop through smoothly. So, remember that the following structure prior to implementing a null object pattern (see Figure 25-3).
    ../images/395506_2_En_25_Chapter/395506_2_En_25_Fig3_HTML.jpg
    Figure 25-3

    The basic structure of a null object pattern

     
  3. 3.
    When should I use this pattern?
    • The pattern is useful if you do not want to encounter with a NullPointerException in Java in some typical scenarios. (For example, if by mistake, you try to invoke a method of a null object.)

    • You can ignore lots of “null checks” in your code.

    • Absence of these null checks make your code cleaner and easily maintainable.

     
  4. 4.
    What are the challenges associated with null object patterns?
    • In some cases, you may want to get closure to the root cause of failure. So, if you throw a NullPointerException that makes more sense to you, you can always handle those exceptions in a try/catch or in a try/catch/finally block and update the log information accordingly.

    • The null object pattern basically helps us to implement a default behavior when you unconsciously want to deal with an object that is not present at all. But this approach may not suite every possible object in a system.

    • Incorrect implementation of a null object pattern can suppress true bags that may appear as normal in your program execution.

    • Creating a proper null object in every possible scenario may not be easy. In some classes, this may cause a change that influences the class methods.

     
  5. 5.

    Null objects work like proxies. Is this correct?

    No. In general, proxies act on real objects at some point of time and they may also provide some behavior. But a null object should not do any such thing.

     
  6. 6.

    The null object pattern is always associated with NullPointerException. Is this correct?

    The concept is same, but the exception name can be different or language specific. For example, in Java, you are using it to guard java.lang.NullPointerException but in a language like C#, you may use this pattern to guard System.NullReferenceException.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.213.44