Design Patterns - Null Object Pattern

design-patterns

Design Patterns - Behavioral - Null Object.

In Null Object pattern, a null object replaces check of NULL object instance. 
Instead of putting if check for a null value, Null Object reflects a do nothing 
relationship. Such Null object can also be used to provide default behaviour in 
case data is not available.

In Null Object pattern, we create an abstract class specifying various operations 
to be done, concrete classes extending this class and a null object class 
providing do nothing implemention of this class and will be used seemlessly 
where we need to check null value.

We are going to create a AbstractCustomer abstract class defining opearations. 
Here the name of the customer and concrete classes extending the 
AbstractCustomer class. A factory class CustomerFactory is created to return 
either RealCustomer or NullCustomer objects based on the name of customer 
passed to it.

// Step 1: Create an abstract class.
public abstract class AbstractCustomer {
   protected String name;
   public abstract boolean isNil();
   public abstract String getName();
}

// Step 2: Create concrete classes extending the above class.
public class RealCustomer extends AbstractCustomer {
   public RealCustomer(String name) {
      this.name = name;        
   }

   public String getName() {
      return name;
   }

   public boolean isNil() {
      return false;
   }
}

public class NullCustomer extends AbstractCustomer {
   public String getName() {
      return "Not Available in Customer Database";
   }

   public boolean isNil() {
      return true;
   }
}

// Step 3: Create CustomerFactory Class.
public class CustomerFactory {
   public static final String[] names = {"Rob", "Joe", "Julie"};

   public static AbstractCustomer getCustomer(String name) {
      for (int i = 0; i < names.length; i++) {
         if (names[i].equalsIgnoreCase(name)){
            return new RealCustomer(name);
         }
      }
      return new NullCustomer();
   }
}

// Step 4: Use the CustomerFactory to get either RealCustomer or 
// NullCustomer objects based on the name of customer passed to it.
AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

System.out.println("Customers");
System.out.println(customer1.getName());
System.out.println(customer2.getName());
System.out.println(customer3.getName());
System.out.println(customer4.getName());

What is the purpose of the Null Object design pattern?

Given that an object reference may be optionally null, and that the result of a null check is to do nothing or use some default value, how can the absence of an object — the presence of a null reference — be treated transparently?

Sometimes a class that requires a collaborator does not need the collaborator to do anything. However, the class wishes to treat a collaborator that does nothing the same way it treats one that actually provides behavior.

The intent of a Null Object is to encapsulate the absence of an object by providing a substitutable alternative that offers suitable default do nothing behavior.

Use the Null Object pattern when:

  1. an object requires a collaborator. The Null Object pattern does not introduce this collaboration—it makes use of a collaboration that already exists
  2. some collaborator instances should do nothing
  3. you want to abstract the handling of null away from the client
// Step 1.  Create an abstract class
public abstract class AbstractCustomer {
   protected String name;
   public abstract boolean isNil();
   public abstract String getName();
}

// Step 2.  Create concrete classes extending the above class.
public class RealCustomer extends AbstractCustomer {
   public RealCustomer(String name) {
      this.name = name;        
   }

   @Override
   public String getName() {
      return name;
   }

   @Override
   public boolean isNil() {
      return false;
   }
}

// Step 3.  Create the null object class
public class NullCustomer extends AbstractCustomer {

   @Override
   public String getName() {
      return "Not Available in Customer Database";
   }

   @Override
   public boolean isNil() {
      return true;
   }
}

// Step 4.  Create the factory class
public class CustomerFactory {
   public static final String[] names = {"Rob", "Joe", "Julie"};

   public static AbstractCustomer getCustomer(String name){

      for (int i = 0; i < names.length; i++) {
         if (names[i].equalsIgnoreCase(name)){
            return new RealCustomer(name);
         }
      }
      return new NullCustomer();
   }
}

// Step 5.  Use the factory method from the factory class
AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

In the above code, we create the AbstractCustomer class, the RealCustomer class, the NullCustomer class, the CustomerFactory class. The NullCustomer class is a stand-in replacement for the RealCustomer class. This code involve using a factory method.

The Null Object class is often implemented as a Singleton. Since a null object usually does not have any state, its state can't change, so multiple instances are identical. Rather than use multiple identical instances, the system can just use a single instance repeatedly.

If some clients expect the null object to do nothing one way and some another, multiple NullObject classes will be required. If the do nothing behavior must be customized at run time, the NullObject class will require pluggable variables so that the client can specify how the null object should do nothing (see the discussion of pluggable adaptors in the Adapter pattern). This may generally be a symptom of the AbstractObject not having a well defined (semantic) interface.

A Null Object does not transform to become a Real Object. If the object may decide to stop providing do nothing behavior and start providing real behavior, it is not a null object. It may be a real object with a do nothing mode, such as a controller which can switch in and out of read-only mode. If it is a single object which must mutate from a do nothing object to a real one, it should be implemented with the State pattern or perhaps the Proxy pattern. In this case a Null State may be used or the proxy may hold a Null Object.

The use of a null object can be similar to that of a Proxy, but the two patterns have different purposes. A proxy provides a level of indirection when accessing a real subject, thus controlling access to the subject. A null collaborator does not hide a real object and control access to it, it replaces the real object. A proxy may eventually mutate to start acting like a real subject. A null object will not mutate to start providing real behavior, it will always provide do nothing behavior.

A Null Object can be a special case of the Strategy pattern. Strategy specifies several ConcreteStrategy classes as different approaches for accomplishing a task. If one of those approaches is to consistently do nothing, that ConcreteStrategy is a NullObject. For example, a Controller is a View's Strategy for handling input, and NoController is the Strategy that ignores all input.

A Null Object can be a special case of the State pattern. Normally, each ConcreteState has some do nothing methods because they're not appropriate for that state. In fact, a given method is often implemented to do something useful in most states but to do nothing in at least one state. If a particular ConcreteState implements most of its methods to do nothing or at least give null results, it becomes a do nothing state and as such is a null state.

A Null Object can be used to allow a Visitor to safely visit a hierarchy and handle the null situation.

Null Object is a concrete collaborator class that acts as the collaborator for a client which needs one. The null behavior is not designed to be mixed into an object that needs some do nothing behavior. It is designed for a class which delegates to a collaborator all of the behavior that may or may not be do nothing behavior.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License