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. public customer(int _Customerid,double _billamount)
  7. {
  8. Customerid = _Customerid;
  9. billamount = _billamount;
  10. }
  11. public int Customerid { get; set; }
  12. public String Customername { get; set; }
  13. public double Discountprice { get; set; }
  14. public double billamount { get; set; }
  15. public virtual void CalculatePrice()
  16. {
  17. billamount =billamount- (billamount * Discountprice) ;
  18. }
  19. public virtual void PrintReceipt()
  20. {
  21. CalculatePrice();
  22. Console.WriteLine("Customer ID:{0},CustomerName:{1},DiscountPrice:{2},Amount to Pay:{3}", Customerid, Customername,
  23. Discountprice, billamount);
  24. }
  25. }
  • 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. //validation of customerid and calcuation of price is intentionally done here.you might have method returning boolean to validate customerid.
  10. foreach (var customer in customers.Where(customer => customer.Customerid==customerid))
  11. {
  12. inputCustomer=new RealCustomer(){Customerid = customer.Customerid,Customername = customer.Customername,billamount = paybillAmount};
  13. return inputCustomer;
  14. }
  15. return inputCustomer;
  16. }
  • 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. }
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. private static customer ValidateCustomerBillpay(int customerid, int paybillAmount)
  8. {
  9. customer inputCustomer =null;
  10. List<customer> customers = new List<customer>()
  11. {
  12. new RealCustomer(){Customerid = 1,Customername = "Rangesh"},
  13. new RealCustomer(){Customerid = 2,Customername = "Sripathi"},
  14. };
  15. foreach (var customer in customers.Where(customer => customer.Customerid == customerid))
  16. {
  17. inputCustomer = new RealCustomer() { Customerid = customer.Customerid, Customername = customer.Customername, billamount = paybillAmount };
  18. return inputCustomer;
  19. }
  20. return inputCustomer=new NullCustomer();
  21. }
Structure of NULL object Pattern