Open In App

Null Object Design Pattern

Improve
Improve
Like Article
Like
Save
Share
Report

The Null Object Design Pattern is a behavioral design pattern that is used to provide a consistent way of handling null or non-existing objects. It is particularly useful in situations where you want to avoid explicit null checks and provide a default behavior for objects that may not exist.

null-object-design-pattern

What is Null Object Design Pattern?

The Null object pattern is a design pattern that simplifies the use of dependencies that can be undefined. This is achieved by using instances of a concrete class that implements a known interface, instead of null references. We create an abstract class specifying various operations to be done, concrete classes extending this class, and a null object class providing do-nothing implementation of this class which will be used seamlessly where we need to check the null value.

Components of Null Object Design Pattern

nullobjectdesignpattern

1. Client

The client is the code that depends on an object that implements a Dependency interface or extends a DependencyBase abstract class. The client uses this object to perform some operation. The client should be able to treat both real and null objects uniformly, without needing to know which type of object it is dealing with.

2. DependencyBase (or Abstract Dependency)

DependencyBase is an abstract class or interface that declares the methods that all concrete dependencies, including the null object, must implement. This class defines the contract that all dependencies must adhere to.

3. Dependency(or Real Dependency)

This class is a functional dependency that may be used by the Client. The client interacts with Dependency objects without needing to know whether they are real or null objects.

4. NullObject(or Null Dependency)

This is the null object class that can be used as a dependency by the Client. It contains no functionality but implements all of the members defined by the DependencyBase abstract class. NullObject represents a null or non-existing dependency in the system. The client can safely call methods on a NullObject without causing errors or needing to perform null checks.

Real-World Analogy of Null Object Design Pattern

Problem Statement:

Consider a car rental service where customers can choose to rent different types of cars.

  • Abstract Dependency: The rental service provides an abstract class or interface called Car that defines the behavior of a car, such as drive() and stop() methods.
  • Real Dependency: The rental service offers various car models as concrete implementations of the Car interface, such as Sedan, SUV, and Convertible. These represent real cars that customers can rent.
  • Null Object: Sometimes, a customer may request a car model that is not available in the rental service’s fleet. Rather than returning a null reference or raising an error, the rental service could provide a NullCar object that implements the Car interface but doesn’t actually represent any specific car model. This way, the customer can still use the drive() and stop() methods without causing issues.
  • Client: The customer renting the car interacts with the rental service’s car objects. They can request a specific car model and use it for their needs. If the requested car model is not available, the rental service provides a NullCar object, ensuring that the customer can still drive and stop the car, even though it doesn’t correspond to a real car model

Example of Null Object Design Pattern

A car rental service allows customers to rent different types of cars. However, some customers may request a car model that is not available in the rental service’s fleet. When this happens, the rental service needs a way to handle this situation gracefully without causing errors or disruptions to the customer.

How Null Object Design Pattern is helpful?

The Null Object Design Pattern is helpful in this situation because it allows the rental service to provide a “null” car object that can be used in place of a real car object when the requested car model is not available. This way, the customer can still use the car object without encountering null reference exceptions or other errors.

nullobjectdesignpatterb

Below is the component wise code of above Example:

1. Abstract Dependency (Car)

Java




interface Car {
    void drive();
    void stop();
}


2. Real Dependency (SUV, Sedan)

Java




class SUV implements Car {
    public void drive() {
        System.out.println("Driving an SUV");
    }
 
    public void stop() {
        System.out.println("Stopping an SUV");
    }
}
 
class Sedan implements Car {
    public void drive() {
        System.out.println("Driving a Sedan");
    }
 
    public void stop() {
        System.out.println("Stopping a Sedan");
    }
}


3. Null Object (NullCar)

Java




class NullCar implements Car {
    public void drive() {
        // Do nothing
    }
 
    public void stop() {
        // Do nothing
    }
}


4. Client (CarRentalService)

Java




class CarRentalService {
    private Car car;
 
    public CarRentalService(Car car) {
        this.car = car;
    }
 
    public void rentCar() {
        car.drive();
        car.stop();
    }
}


Complete Code of above example

Below is the complete code of the above example:

Java




interface Car {
    void drive();
    void stop();
}
 
class SUV implements Car {
    public void drive() {
        System.out.println("Driving an SUV");
    }
 
    public void stop() {
        System.out.println("Stopping an SUV");
    }
}
 
class Sedan implements Car {
    public void drive() {
        System.out.println("Driving a Sedan");
    }
 
    public void stop() {
        System.out.println("Stopping a Sedan");
    }
}
 
class NullCar implements Car {
    public void drive() {
        // Do nothing
    }
 
    public void stop() {
        // Do nothing
    }
}
 
class CarRentalService {
    private Car car;
 
    public CarRentalService(Car car) {
        this.car = car;
    }
 
    public void rentCar() {
        car.drive();
        car.stop();
    }
}
 
public class Main {
    public static void main(String[] args) {
        Car suv = new SUV();
        Car sedan = new Sedan();
        Car nullCar = new NullCar();
 
        CarRentalService rentalService1 = new CarRentalService(suv);
        CarRentalService rentalService2 = new CarRentalService(sedan);
        CarRentalService rentalService3 = new CarRentalService(nullCar);
 
        rentalService1.rentCar(); // Output: Driving an SUV, Stopping an SUV
        rentalService2.rentCar(); // Output: Driving a Sedan, Stopping a Sedan
        rentalService3.rentCar(); // No output
    }
}


Output




Driving an SUV
Stopping an SUV
Driving a Sedan
Stopping a Sedan


Below is the explanation of the above code:

  • The Car interface defines the behavior of cars with drive() and stop() methods.
  • SUV and Sedan classes are real implementations of the Car interface, representing actual cars that can be rented.
  • NullCar class is a null object implementation of the Car interface, providing default or no-op implementations of drive() and stop() methods.
  • The CarRentalService class is a client that interacts with the Car objects. It has a method rentCar() that calls drive() and stop() on the Car object, regardless of whether it is a real car or a null car.

When to use the Null Object Design Pattern

The Null Object Design Pattern is useful in situations where you want to provide a default or no-op implementation of an object’s behavior to avoid null checks and handle null references gracefully. Here are some scenarios where the Null Object Design Pattern can be beneficial:

  • Default Behavior: When you want to provide a default behavior for an object when its actual implementation is not available or applicable.
  • Avoid Null Checks: When you want to avoid explicit null checks in your code by providing a null object implementation that can be safely used in place of a null reference.
  • Consistent Interface: When you want to provide a consistent interface for clients to interact with, regardless of whether they are dealing with real or null objects.
  • Simplifying Client Code: When you want to simplify client code by allowing them to treat null objects the same way as real objects, without needing to handle null references separately.

When not to use the Null Object Design Pattern

There are some cases where the Null Object Design Pattern may not be suitable:

  • Complex Behavior: When the null object needs to implement complex behavior or maintain state, it may not be appropriate to use the Null Object Design Pattern, as it is intended for providing simple default behavior.
  • Performance Considerations: If creating and using null objects adds significant overhead or complexity to the system, it may be better to handle null references explicitly in the code.
  • Confusion with Real Objects: If there is a risk of confusion between null objects and real objects in the system, it may be better to use explicit null checks to make the code more explicit and readable.



Last Updated : 08 Mar, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads