Introduction
In this article we will try to understand the Null object pattern and see how this pattern can help us to write more robust code. We will implement a contrived example to demonstrate this pattern.
Background
There is no developer in the world who has not encounters a "Null reference exception
" in his development career. What is this exception? Well this exception simply tell that we are trying to access to deference a variable with NULL value. Which brings another question, what is a NULL value? NULL in most programming languages indicates the absence of a value. For any variable, if the value is NULL, it would mean that there is no value associated with that variable and thus the caller should not perform any operation using this variable.
From a language perspective, NULL is very important and does makes sense as there are scenarios where we want to represent the absence of value in a variable. But from the code maintainability perspective, it would mean that whenever we are getting an object from another module/class/service, we should always check if it is null or not before using this variable. This could lead to more complex and unmaintainable code.
Now the Null object pattern try to ease this problem. This pattern suggests that whenever we have dependent modules returning objects to each other, we should always return an object. The absence of a valid value should also be represented as a special object in this case. This can be implemented creating and returning instances of a concrete class that implements the same interface the valid class implements. but it will do nothing whenever any operations are performed on it. What this will do is that it make the calling code much simpler since the calling code will not have to check for null but simply call the methods on the return object.
If we try to visualize the Null object pattern, it will look something like following.
Client
. This class that requires the instance of an object to use. DependencyBase
. This is an interface that all the concrete classes(that could be returned to client) will implement. Dependency
. This is the actual concrete classes(that could be returned to client) NullObject
. This is our Null Object class. This will be returned to the client whenever we want to return NO-VALUE or absence of value to the client. In other words, this is the class that will be returned to the client in all the scenarios where otherwise we would have returned null. Its important to note that this class will implement the DependencyBase
interface but will provide a DO-NOTHING implementation for the interface since absence of value means no operations should be possible.
Now before we move on to see how this could be implemented, let us discuss when it makes sense to use this pattern. If we try to use this pattern for all possible classes in the system, we will end up increasing the complexity of the system rather than reducing it.
It only makes sense to use this pattern when we have some class that is expecting another class and is using some sort of factory, service locator or strategy to get the instance of this class. This will ensure that whenever a dependency is being pulled from external module, we don't have to worry about the NULL values but rather the external system will return a NULL Object which can safely be used for do nothing type of operations.
Using the code
Now let us look into a very contrived example to understand this pattern. First let us try to understand the problem by not using a Null Object.
In this example, we will ask the user to select a shipping method(yes yet another e-commerce example). Based on the users selection, we will select the appropriate shipping strategy and pass it to the OrderProcessor
class. OrderProcessor
class will use it to schedule the shipping. So lets start by looking at our interface that represent the shipping strategy i.e. IShippingStrategy
.
public interface IShippingStrategy
{
void ScheduleShipping();
}
Once we have this interface ready, lets look at the concrete classes for shipping. The reason for these separate classes is because each of this class will call a web service of the respective shipping service provider to schedule a shipping(In real world, here we are just logging method call on console).
public class DHLShippingStrategy : IShippingStrategy
{
public void ScheduleShipping()
{
Console.WriteLine("DHL Shipping has been scheduled");
}
}
public class FedExShippingStrategy : IShippingStrategy
{
public void ScheduleShipping()
{
Console.WriteLine("FedEx Shipping has been scheduled");
}
}
public class InHouseShippingStrategy : IShippingStrategy
{
public void ScheduleShipping()
{
Console.WriteLine("In House Shipping has been scheduled");
}
}
Now lets look at our OrderProcessor
class which will schedule shipping using the passed in IShippingStrategy
.
class OrderProcessor
{
internal void ProcessOrder(IShippingStrategy shippingStrategy = null)
{
if (shippingStrategy != null)
{
shippingStrategy.ScheduleShipping();
}
else
{
Console.WriteLine("Invalid Shipping Strategy");
}
}
}
And finally lets look at the code that is mimicking the selection of shipping method from the user.
static void Main(string[] args)
{
Console.WriteLine("Please select the Shipping method 1. FexEd 2. DHL 3. In House Shipping");
string input = Console.ReadLine();
int choice = 0;
bool result = Int32.TryParse(input, out choice);
if(result == true)
{
OrderProcessor orderProcessor = new OrderProcessor();
IShippingStrategy shippingStrategy = null;
switch(choice)
{
case 1:
shippingStrategy = new FedExShippingStrategy();
break;
case 2:
shippingStrategy = new DHLShippingStrategy();
break;
case 3:
shippingStrategy = new InHouseShippingStrategy();
break;
}
orderProcessor.ProcessOrder(shippingStrategy);
}
Console.ReadLine();
}
Now the problem we have talked about the NULL values is quite evident when we look at the OrderProcessor
code.
The order processor is checking whether the passed strategy is null or not. If it is not then some action is being taken otherwise some different action. Now there are two issue with this
- The code is messy due to all these null checks and
- If an invalid shipping method is selected something needs to be done apart from just logging. Which would mean that this invalid case handling logic will also come in the
OrderProcessor
class which is direct violation of Single Responsibility Principle.
Welcome Null Object
So to avoid this problem, let us introduce a Null Object
on our application. Lets call this class InvalidShippingStrategy
class InvalidShippingStrategy : IShippingStrategy
{
public void ScheduleShipping()
{
Console.WriteLine("Invalid Shipping Strategy");
}
}
Now with this null object in place, we need to modify the strategy creation code to cater to the invalid scenario too.
static void Main(string[] args)
{
Console.WriteLine("Please select the Shipping method 1. FexEd 2. DHL 3. In House Shipping");
string input = Console.ReadLine();
int choice = 0;
bool result = Int32.TryParse(input, out choice);
if(result == true)
{
OrderProcessor orderProcessor = new OrderProcessor();
IShippingStrategy shippingStrategy = new InvalidShippingStrategy();
switch(choice)
{
case 1:
shippingStrategy = new FedExShippingStrategy();
break;
case 2:
shippingStrategy = new DHLShippingStrategy();
break;
case 3:
shippingStrategy = new InHouseShippingStrategy();
break;
}
orderProcessor.ProcessOrder(shippingStrategy);
}
Console.ReadLine();
}
And the best part is that the OrderProcessor
will not have to worry about the invalid shipping selection at all. All the code to handle the invalid shipping method can be put inside our Null Object i.e. InvalidShippingStrategy
class.
class OrderProcessor
{
internal void ProcessOrder(IShippingStrategy shippingStrategy)
{
shippingStrategy.ScheduleShipping();
}
}
This represents a very basic implementation of Null Object pattern.
Singleton should help
Now when we look at our Null Object, its behavior is same irrespective of any number of instances we create for it i.e. It will handle the NO-VALUE-DO-NOTHING
scenario. So does is really makes sense to have multiple instances of this class. Perhaps not. So an optimization could be to make this class singleton.
class InvalidShippingStrategy : IShippingStrategy
{
private static readonly InvalidShippingStrategy instance = null;
static InvalidShippingStrategy()
{
instance = new InvalidShippingStrategy();
}
public static InvalidShippingStrategy Instance
{
get
{
return instance;
}
}
public void ScheduleShipping()
{
Console.WriteLine("Invalid Shipping Strategy");
}
}
we need to modify the strategy creation code to cater to the invalid scenario too so the program.cs will now do something like:
IShippingStrategy shippingStrategy = InvalidShippingStrategy.Instance;
Now we have a singleton Null object and thus only one instance of this Null object will be there in the system and thus some performance gain.
Note: The important thing to remember about this pattern is that it should be used whenever we see multiple classes collaborating by passing objects. Some examples for this could be strategy
pattern, factory
pattern, Service Locator
, Command
pattern, Repository
Pattern etc. Having this pattern for all classes is not a good idea and could lead to more complex code.
Points of interest
In this article we have looked at Null Object pattern. We have seen how this pattern can make the calling code much cleaner with no null checks. We have also looked at a very basic(and not so real world) implementation of the pattern. This has been written from beginner's perspective. I hope this has been informative.
History
- 21 January, 2016 - First version