Null Object in Design Pattern

Motivation
 
"There are frequent cases where you indicate the absence of an object using "null". Instead of using null you could also use the Null Object that provides do-nothing behavior."
 
Let's try to understand the motivation with an example. Assume you create a simple system where a customer inputs a customer id and bill amount. Your output should be the price after applying the discount, provided he has a valid customer id.
 
Example
  • Let's create an abstract class named customer that accepts a customerid and billamount as input and a public method that outputs the price that the customer must pay after applying a discount.
  1. internal abstract class customer  
  2.    {  
  3.        public customer()  
  4.        {  
  5.              
  6.        }  
  7.        public customer(int _Customerid,double _billamount)  
  8.        {  
  9.            Customerid = _Customerid;  
  10.            billamount = _billamount;  
  11.        }  
  12.        public int Customerid { getset; }  
  13.        public String Customername { getset; }  
  14.        public double Discountprice { getset; }  
  15.        public double billamount { getset; }  
  16.        public virtual void CalculatePrice()  
  17.        {  
  18.            billamount =billamount- (billamount * Discountprice) ;  
  19.            
  20.        }  
  21.   
  22.        public virtual void PrintReceipt()  
  23.        {  
  24.            CalculatePrice();  
  25.            Console.WriteLine("Customer ID:{0},CustomerName:{1},DiscountPrice:{2},Amount to Pay:{3}", Customerid, Customername,  
  26.                Discountprice, billamount);  
  27.        }  
  28.    }  
  • Let's create a class named RealCustomer that implements customer. You can have this class for setting the discountprice.
  1. class RealCustomer : customer  
  2.   {  
  3.       public RealCustomer()  
  4.       {  
  5.            this.Discountprice = 0.1;
  6.             CalculatePrice()
  7.       }  
  8.       public RealCustomer(int _customerid,double _billamount) : base(_customerid,_billamount)  
  9.       {  
  10.             this.Discountprice = 0.1;
  11.       }  
  12.   }  
  • Let's test our system by supplying the input id and bill amount from the Main method. 
  1. private static customer ValidateCustomerBillpay(int customerid,int paybillAmount)  
  2.        {  
  3.            customer inputCustomer = null;  
  4.            List<customer> customers = new List<customer>()  
  5.            {  
  6.                new RealCustomer(){Customerid = 1,Customername = "Rangesh"},  
  7.                new RealCustomer(){Customerid = 2,Customername = "Sripathi"},  
  8.            };  
  9.    
  10.             //validation of customerid and calcuation of price is intentionally done here.you might have method returning boolean to                validate customerid.   
  11.            foreach (var customer in customers.Where(customer => customer.Customerid==customerid))  
  12.            {  
  13.               inputCustomer=new RealCustomer(){Customerid = customer.Customerid,Customername = customer.Customername,billamount = paybillAmount};  
  14.                return inputCustomer;  
  15.            }  
  16.            return inputCustomer;  
  17.        }  
  •  And our Main method looks as in the following:
  1. var customer = ValidateCustomerBillpay(20, 1200); //Supplied wrong CustomerId would return null. 
  2.          if (customer != null)  //Look at the if block where null object pattern is applicable
  3.          {  
  4.              customer.PrintReceipt();   
  5.          }  
The preceding does get the job done, but the following are the reasons why  it is advised to use a Null Object pattern:
  • Clean code. You don't want your if block to spread across classes for determining null checks. It's more towards OOP.
  • Caller code (in our case MainMethod) need not care whether it's a Nullobject or a real object.
  • Less branching, in other words lower code complexity. 
Does that mean a NullObject must be used everywhere when you make a check for Null?
No, it is advised to use the pattern when you have a collaborator to an object (in our case PrintReceipt).
 
Let's try to implement a Null Object pattern for the preceding system. All we must do is to extend the Customer to NullCustomer. The code looks as in the following:
  1. class NullCustomer : customer  
  2.     {  
  3.         public NullCustomer()  
  4.         {    
  5.             this.Customername = "Invalid Customer";  
  6.             //I do Nothing here..do not call my calculateprice method.  
  7.         }  
  8.   
  9.     }  
Modified Main Method
  1. static void Main(string[] args)  
  2.       {  
  3.           //Supplying inccorrect customerid,i have removed the if block :)   
  4.           var customer = ValidateCustomerBillpay(20, 1200);  
  5.           customer.PrintReceipt();  
  6.       }  
  7.   
  8.       private static customer ValidateCustomerBillpay(int customerid, int paybillAmount)  
  9.       {  
  10.           customer inputCustomer =null;  
  11.           List<customer> customers = new List<customer>()  
  12.           {  
  13.               new RealCustomer(){Customerid = 1,Customername = "Rangesh"},  
  14.               new RealCustomer(){Customerid = 2,Customername = "Sripathi"},  
  15.           };  
  16.   
  17.   
  18.           foreach (var customer in customers.Where(customer => customer.Customerid == customerid))  
  19.           {  
  20.               inputCustomer = new RealCustomer() { Customerid = customer.Customerid, Customername = customer.Customername, billamount = paybillAmount };  
  21.               return inputCustomer;  
  22.           }  
  23.           return inputCustomer=new NullCustomer();  
  24.       }  
Structure of NULL object Pattern
 

X

Build smarter apps with Machine Learning, Bots, Cognitive Services - Start free.

Start Learning Now