© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_25

25. Null Object Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

This chapter covers the Null Object pattern.

Definition

The Null Object pattern is not a GoF design pattern. I am taking the definition from Wikipedia, which says the following.

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.

Concept

The pattern can implement a “do-nothing” relationship, or it can provide a default behavior when an application encounters a null object instead of a real object. With this pattern, our core aim is to make a better solution by avoiding a “null objects check” or “null collaborations check” through if blocks and you encapsulate the absence of an object by providing a default behavior that does nothing. The basic structure of this pattern is shown in Figure 25-1.
../images/463942_2_En_25_Chapter/463942_2_En_25_Fig1_HTML.jpg
Figure 25-1

The basic structure of a Null Object pattern

This chapter begins with a program that seems to be OK, but it has a serious potential bug. When you analyze the bug with a potential solution, you understand the need for the Null Object pattern. So, let’s jump to the next section.

A Faulty Program

Let’s assume that you have two different types of vehicles: Bus and Train, and a client can pass different input (e.g., a and b) to create a Bus object or a Train object. The following program demonstrates this. This program runs smoothly when the input is valid, but a potential bug is revealed when you supply an invalid input. Here’s the faulty program.
using System;
namespace ProgramWithOnePotentialBug
{
    interface IVehicle
    {
        void Travel();
    }
    class Bus : IVehicle
    {
        public static int busCount = 0;
        public Bus()
        {
            busCount++;
        }
        public void Travel()
        {
            Console.WriteLine("Let us travel with Bus");
        }
    }
    class Train : IVehicle
    {
        public static int trainCount = 0;
        public Train()
        {
            trainCount++;
        }
        public void Travel()
        {
            Console.WriteLine("Let us travel with Train");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***This program demonstrates the need of null object pattern.*** ");
            string input = String.Empty;
            int totalObjects = 0;
            while (input != "exit")
            {
                Console.WriteLine("Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.");
                input = Console.ReadLine();
                IVehicle vehicle = null;
                switch (input)
                {
                    case "a":
                        vehicle = new Bus();
                        break;
                    case "b":
                        vehicle = new Train();
                        break;
                    case "exit":
                        Console.WriteLine("Creating one more bus and closing the application");
                        vehicle = new Bus();
                        break;
                }
                totalObjects = Bus.busCount + Train.trainCount;
                vehicle.Travel();
                Console.WriteLine($"Total objects created in the system ={totalObjects}");
            }
        }
    }
}

Output with Valid Input

You may have an immediate concern; when you type exit, you create an unnecessary object. It’s true. We’ll handle it later. For now, let’s focus on the other bug, which is more dangerous for us. Here is some output with valid input.
***This program demonstrates the need of null object pattern.***
Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.
a
Let us travel with Bus
Total objects created in the system =1
Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.
b
Let us travel with Train
Total objects created in the system =2
Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.
a
Let us travel with Bus
Total objects created in the system =3
Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.

Analysis with an Unwanted Input

Let’s assume that the user has mistakenly supplied a different character, such as e, as shown here.
Enter your choice(Type 'a' for Bus, 'b' for Train.Type 'exit' to quit application.
e
This time, you get a runtime exception called System.NullReferenceException, as shown in Figure 25-2.
../images/463942_2_En_25_Chapter/463942_2_En_25_Fig2_HTML.jpg
Figure 25-2

A runtime exception occurs when the user supplies an invalid input

A Potential Fix

The immediate remedy that may come into your mind is to do a null check before invoking the operation, as shown here.
if (vehicle != null)
{
  vehicle.Travel();
}

Analysis

The prior solution works in this case. But think of an enterprise application. When you do null checks for each scenario, if you place if conditions like this in each case, you make your code dirty. At the same time, you may notice the side effect of difficult maintenance. The concept of Null Object pattern is useful in similar cases.

Point to Remember
In the prior example, I can avoid creating unnecessary objects when the user types exit and avoid the null check if I use a null conditional operator like the following:
vehicle?.Travel();

This operator is available in C# 6 and later versions only. Still it can be beneficial for you to look into the implementation details of the Null Object pattern. For example, when you use Null Object pattern, instead of doing nothing, you can supply a default behavior (that suits your application best) for those null objects.

Real-World Example

A washing machine works properly when there is a water supply without any internal leakage. But suppose that on one occasion, you forget to supply the water before you start washing the clothes, but you pressed the button that initiates washing the clothes. The washing machine should not damage itself in such a situation; so, it can beep some alarm to draw your attention and indicate that there is no water supply at the moment.

Computer-World Example

Assume that in a client-server architecture, the server does calculations based on the client input. The server needs to be intelligent enough not to initiate any calculation unnecessarily. Before processing the input, it may want to do a cross-verification to ensure whether it needs to start the calculation at all, or it should ignore an invalid input. You may notice the Command pattern with a Null Object pattern in such a case.

Basically, in an enterprise application, you can avoid big number of null checks and if/else blocks using this design pattern. The following implementation gives an overview of this pattern.

Implementation

Let’s modify the faulty program that we discussed before. You handle the invalid input through a NullVehicle object this time. So, if by mistake the user supplies any invalid data (in other words, any input other than a or b in this case), the application does nothing; that is, it can ignore those invalid input through a NullVehicle object , which does nothing. The class is defined as follows.
/// <summary>
/// NullVehicle class
/// </summary>
class NullVehicle : IVehicle
{
 private static readonly NullVehicle instance = new NullVehicle();
 private NullVehicle()
 {
  nullVehicleCount++;
  }
 public static int nullVehicleCount;
 public static NullVehicle Instance
 {
  get
  {
    return instance;
  }
 }
 public void Travel()
{
   // Do Nothing
}
}

You can see that I applied the concept of Singleton design pattern when I create a NullVehicle object . It is because there can be an infinite number of invalid input, so in the following example, I do not want to create the NullVehicle object repeatedly. Once there is a NullVehicle object, I’d like to reuse that object.

Note

For a null object method, you need to return whatever seems sensible as a default. In our example, you cannot travel with a vehicle that does not exist. So, it makes sense that for the NullVehicle class, the Travel() method does nothing.

Class Diagram

Figure 25-3 shows the class diagram.
../images/463942_2_En_25_Chapter/463942_2_En_25_Fig3_HTML.jpg
Figure 25-3

Class diagram

Solution Explorer View

Figure 25-4 shows the high-level structure of the program.
../images/463942_2_En_25_Chapter/463942_2_En_25_Fig4_HTML.jpg
Figure 25-4

Solution Explorer view

Demonstration

Here’s the complete implementation.
using System;
namespace NullObjectPattern
{
    interface IVehicle
    {
        void Travel();
    }
    /// <summary>
    /// Bus class
    /// </summary>
    class Bus : IVehicle
    {
        public static int busCount = 0;
        public Bus()
        {
            busCount++;
        }
        public void Travel()
        {
            Console.WriteLine("Let us travel with Bus.");
        }
    }
    /// <summary>
    /// Train class
    /// </summary>
    class Train : IVehicle
    {
        public static int trainCount = 0;
        public Train()
        {
            trainCount++;
        }
        public void Travel()
        {
            Console.WriteLine("Let us travel with Train.");
        }
    }
    /// <summary>
    /// NullVehicle class
    /// </summary>
    class NullVehicle : IVehicle
    {
        private static readonly NullVehicle instance = new NullVehicle();
        private NullVehicle()
        {
            nullVehicleCount++;
        }
        public static int nullVehicleCount;
        public static NullVehicle Instance
        {
            get
            {
                return instance;
            }
        }
        public void Travel()
        {
            // Do Nothing
        }
    }
    /// <summary>
    /// Client code
    /// </summary>
    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Null Object Pattern Demonstration.*** ");
            string input = String.Empty;
            int totalObjects = 0;
            while (input != "exit")
            {
                Console.WriteLine("Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit) ");
                input = Console.ReadLine();
                IVehicle vehicle = null;
                switch (input)
                {
                    case "a":
                        vehicle = new Bus();
                        break;
                    case "b":
                        vehicle = new Train();
                        break;
                    case "exit":
                        Console.WriteLine("Closing the application.");
                        vehicle = NullVehicle.Instance;
                        break;
                    default:
                        Console.WriteLine("Please supply the correct input(a/b/exit)");
                        vehicle = NullVehicle.Instance;
                        break;
                }
                totalObjects = Bus.busCount + Train.trainCount + NullVehicle.nullVehicleCount;
                // No need to do null check now.
                //if (vehicle != null)
                vehicle.Travel();
                //}
                Console.WriteLine("Total objects created in the system ={0}",
                totalObjects);
            }
            Console.ReadKey();
        }
    }
}

Output

Here’s the output.
***Null Object Pattern Demonstration.***
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
a
Let us travel with Bus.
Total objects created in the system =2
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
b
Let us travel with Train.
Total objects created in the system =3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
c
Please supply the correct input(a/b/exit)
Total objects created in the system =3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
d
Please supply the correct input(a/b/exit)
Total objects created in the system =3
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
b
Let us travel with Train.
Total objects created in the system =4
Enter your choice( Type 'a' for Bus, 'b' for Train.Type 'exit' to quit)
exit
Closing the application.
Total objects created in the system =4

Analysis

I draw your attention to the following points.
  • Invalid input and their effects are shown in bold.

  • The objects count is not increasing because of null vehicle objects/invalid input.

  • You did not perform any null check. Still, the program execution is not interrupted because of invalid user input.

Q&A Session

25.1 At the beginning of the implementation, I see an additional object is created. Is this intentional?

To save some computer memory/storage, I followed a Singleton design pattern that supports early initialization when I constructed the NullVehicle class . You do not want to create a NullVehicle object for each invalid input because your application may receive a large number of invalid input. If you do not guard against the situation, a huge number of NullVehicle objects may reside in the system (which is useless), and they can occupy a large amount of computer memory, which in turn can cause some unwanted side effects. (For example, the system may become slow, applications response time may increase, etc.)

25.2 When should you use this pattern?

This pattern can be useful in the following cases.
  • You do not want to encounter a NullReferenceException (for example, if by mistake you try to invoke a method of a null object).

  • You like to ignore lots of null checks in your code.

  • You want to make your code cleaner and easily maintainable.

Note

You learn another use of this pattern at the end of this chapter.

25.3 What are the challenges associated with the Null Object pattern?

You need to be aware of the following scenarios.
  • Most often, you may want to find and fix the root cause of a failure. So, if you throw a NullReferenceException, that can work better for you. You can always handle those exceptions in a try/catch block or a try/catch/finally block and update the log information accordingly.

  • The Null Object pattern helps you to implement a default behavior when you unconsciously want to deal with an object that is not present at all. But trying to supply such a default behavior may not always be appropriate.

  • Incorrect implementations of the Null Object pattern can suppress the true bug that may appear as normal in your program execution.

25.4. It looks as if null objects are working like proxies. Is this correct?

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

25.5. The Null Object pattern is always associated with NullReferenceException. Is this correct?

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

Finally, I want to draw your attention to another interesting point. The Null Object pattern can be useful in another context. For example, consider the following segment of code.
            //A case study in another context.
            List<IVehicle> vehicleList = new List<IVehicle>();
            vehicleList.Add(new Bus());
            vehicleList.Add(new Train());
            vehicleList.Add(null);
            foreach (IVehicle vehicle in vehicleList)
            {
                vehicle.Travel();
            }

When you use the previous code segment, you get System.NullReferenceException again. But if you replace vehicleList.Add(null); with vehicleList.Add(NullVehicle.Instance);, there is no runtime exception. So, you can loop through easily, which is another important usage of this pattern.

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

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