Null Object Design Pattern in .NET Core

Introduction

A behavioral design pattern called the Null Object Pattern offers an object to represent an interface's missing object. In cases when a null object would result in a null reference exception, it's a means to provide an alternative behavior. We'll go deep into the C# Null Object Pattern in this article, working our way up to more complex situations.

The Null Object Design Pattern What is it?

One design approach that makes using dependencies that may be undefined easier is the Null object pattern. Instead of using null references, this is accomplished by using instances of a concrete class that implements a recognized interface. An abstract class outlining the different actions to be performed is created, along with concrete classes that extend it and a null object class that offers a do-nothing version of the class that can be used anywhere we need to check the null value.

Components of the Null Object Design Pattern


Client

The code that depends on an object that extends an abstract DependencyBase class or implements a Dependency interface is called the client. This object is used by the client to carry out a task. It should be possible for the client to handle both real and null objects in the same way without having to be aware of which kind of object it is working with.

DependencyBase or Abstract Dependency

An abstract class or interface called DependencyBase specifies the methods that must be implemented by all concrete dependents, including the null object. The contract that all dependencies have to abide by is defined by this class.

Dependency or Real Dependency

The Client may utilize this class as a functional dependency. It is not necessary for the client to know if Dependency objects are actual or null in order to interact with them.

NullObject or Null Dependency

This is the class of null objects that the client may utilize as a dependency. While it implements every member specified by the DependencyBase abstract class, it lacks functionality. A null or non-existent dependency in the system is represented by a NullObject. Methods on a NullObject can be securely called by the client without resulting in errors or requiring null checks.

As an illustration,

Abstract Dependency

Here is the code for the ICar.cs file.

public interface ICar
{
    void Drive();
    
    void Stop();
}

Real Dependency

Here is the code for the SedanCar.cs file.

public class SedanCar : ICar
{
    public void Drive()
    {
        Console.WriteLine("Drive the sedan car.");
    }

    public void Stop()
    {
        Console.WriteLine("Stop the sedan car.");
    }
}

Null Object Dependency

Here is the code for the NullCar.cs file.

public class NullCar : ICar
{
    public void Drive()
    {

    }

    public void Stop()
    {

    }
}

Client

Here is the code for the CarService.cs file.

public class CarService(ICar car)
{
    private readonly ICar _car = car;

    public void Run()
    {
        Console.WriteLine($"Start run method. {nameof(ICar)}: {_car}");
        _car.Drive();
        _car.Stop();

        Console.WriteLine($"Complete run method. {nameof(ICar)}: {_car}");
        Console.WriteLine();
    }
}

Program

Here is the code for the Program.cs file.

var sedanCar = new SedanCar();
var carService = new CarService(sedanCar);

carService.Run();

var nullCar = new NullCar();
carService = new CarService(nullCar);

carService.Run();

Output

Output

When to apply the Design Pattern for Null Objects?

When you want to provide a default or no-op implementation of an object's functionality to avoid null checks and handle null references gracefully, you can use the Null Object Design Pattern. The following situations call for the application of the Null Object Design Pattern.

  • Default Behavior: This is the behavior you want to give an object in the event that its real implementation is unavailable or inappropriate.
  • Avoid Null Checks: When you want to provide a null object implementation that may be used safely in place of a null reference, you can avoid having to do explicit null checks in your code.
  • Consistent Interface: In situations when you need to give customers access to an interface that stays the same whether they are working with real or null objects.
  • Simplifying Client Code: When you wish to spare client code from handling null references by letting them handle null objects in the same manner as actual objects.

When the Null Object Design Pattern should not be used?

The Null Object Design Pattern might not be appropriate in the following situations.

  • Sophisticated Behavior: The Null Object Design Pattern is meant to provide simple default behavior; therefore, it might not be acceptable when the null object needs to implement sophisticated behavior or store state.
  • Performance Considerations: It could be preferable to handle null references directly in the code if generating and utilizing null objects significantly increases overhead or complexity in the system.
  • Confusion with Real Objects: Explicit null checks may be preferable in order to improve the readability and clarity of the code if there is a chance that null objects and real objects could be confused in the system.

Summary

A solid method for handling the lack of objects is provided by the Null Object Pattern, a design pattern. It lowers the possibility of runtime mistakes and simplifies client code by offering a default behavior and doing away with the necessity for null checks. To improve stability and maintainability, the Null Object Pattern can be a useful tool when building a new system or restructuring an old one.

We learned the new technique and evolved together.

Happy coding!