Design Patterns: Null Object Pattern

The null reference pattern is a very simple and useful pattern. When implemented properly, it can greatly reduce the amount of code by eliminating all the checks and guards against null object references. The pattern is used to shield clients from having to handle a null case when a service or an objects returns a null. This is accomplished by returning an object that is a ‘null implementation’ for the case when no result is found. This is common when the design follows a strategy-like pattern, where an object takes in an instance of another dependent object and invokes an operation on it. In some cases, the passed in object returns no results or fails to return a result. Rather than returning back a null reference, an instance of a null-object implementation is returned. The Null Object pattern is also know as the ‘Stub’ or the ‘Active Null’ pattern.

Example
Consider the following ‘Square’ that implements the ‘IShape’ interface. The ‘ShapeService’ domain class is used to manipulate various IShapes and retrieve them from the database. The GetShapeById() function in the ShapeService class takes in an integer ID of a shape and locates it in the database. However, if no shape is found with the specified ID, the service will return null.

IShape.cs

public interface IShape
{
     void Draw();
}

Square.cs

public class Square : IShape
{
     public void Draw()
     {
          Console.WriteLine("Drawing a Square...");
     }
}

ShapeService.cs

public class ShapeService
{
     public IShape GetShapeById(int id)
     {
            var square = GetShapeFromDatabase(id); // Details of method removed for simplicity
            return square;            
     }
}

Program.cs and Main

public class Program
{
     public static void Main()
     {
          var shapeService = new ShapeService();
          var shape = shapeService.GetShapeById(3);

          shape.Draw(); // Can cause a null reference exception
      }
}

In the example above, since null is a possible value returned by the service, our client code will need to account for that possibility and guard against a null reference exception.

Program.cs and Main w/ Exception handling

public class Program
{
     public static void Main()
     {
          var shapeService = new ShapeService();
          var shape = shapeService.GetShapeById(3);

          if (shape != null)
            {
                shape.Draw();
            }
      }
}

The code in the last main method is enhanced to check for a null reference before attempting to invoke the ‘Draw()’ method. While this works and handles the null reference exception properly, it does clutter our code and it puts the responsibility on the client to check and account the case when a null is returned. This can be prone to error as every consumer of the service is responsible to check for the null case.

Using the Null Object Approach
Rather then relying on the client to perform the proper checks for null references, it is better to revert the approach and enhance the service not never return null. This is accomplished by introducing a new implementation of the IShape interface specifically for handling the null case. This new implementation defines the ‘Draw()’ function in a way that the function performs no operations. This way, when invoked it will do nothing. A null implementation of the IShape interface will look like the following:

NullShape.cs

public class NullShape : IShape
{
     public void Draw()
     {
          // Do Nothing
     }
}

Next, the GetShapeById() method in the ShapeService class can be enhanced to return a null object rather than returning null:

ShapeService.cs

public class ShapeService
{
     public IShape GetShapeById(int id)
     {
          var square = GetShapeFromDatabase(id);

          return square ?? new NullShape();
      }
}

With these changes, the GetShapeById() function is guaranteed to return an object that implements the IShape interface. The implementation of the Draw() method on NullShape class does nothing. However, the client calling the method can now safely invoke the Draw() method on all returned objects without having to worry about a null reference exception. The Main() method can be simplified be eliminating the check for null:

Program.cs and Main

public class Program
{
     static void Main()
     {
          var shapeService = new ShapeService();
          var shape = shapeService.GetShapeById(3);

          shape.Draw(); // No need to check for null
      }
}

This is clearly an over simplified example. However, in larger implementations, using the null object pattern can eliminate all the checks for null references and greatly reduce the clutter in the code.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *