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.
- internal abstract class customer
- {
- public customer()
- {
-
- }
- public customer(int _Customerid,double _billamount)
- {
- Customerid = _Customerid;
- billamount = _billamount;
- }
- public int Customerid { get; set; }
- public String Customername { get; set; }
- public double Discountprice { get; set; }
- public double billamount { get; set; }
- public virtual void CalculatePrice()
- {
- billamount =billamount- (billamount * Discountprice) ;
-
- }
-
- public virtual void PrintReceipt()
- {
- CalculatePrice();
- Console.WriteLine("Customer ID:{0},CustomerName:{1},DiscountPrice:{2},Amount to Pay:{3}", Customerid, Customername,
- Discountprice, billamount);
- }
- }
- Let's create a class named RealCustomer that implements customer. You can have this class for setting the discountprice.
- class RealCustomer : customer
- {
- public RealCustomer()
- {
- this.Discountprice = 0.1;
- CalculatePrice()
- }
- public RealCustomer(int _customerid,double _billamount) : base(_customerid,_billamount)
- {
- this.Discountprice = 0.1;
- }
- }
- Let's test our system by supplying the input id and bill amount from the Main method.
- private static customer ValidateCustomerBillpay(int customerid,int paybillAmount)
- {
- customer inputCustomer = null;
- List<customer> customers = new List<customer>()
- {
- new RealCustomer(){Customerid = 1,Customername = "Rangesh"},
- new RealCustomer(){Customerid = 2,Customername = "Sripathi"},
- };
-
- //validation of customerid and calcuation of price is intentionally done here.you might have method returning boolean to validate customerid.
- foreach (var customer in customers.Where(customer => customer.Customerid==customerid))
- {
- inputCustomer=new RealCustomer(){Customerid = customer.Customerid,Customername = customer.Customername,billamount = paybillAmount};
- return inputCustomer;
- }
- return inputCustomer;
- }
- And our Main method looks as in the following:
- var customer = ValidateCustomerBillpay(20, 1200); //Supplied wrong CustomerId would return null.
- if (customer != null) //Look at the if block where null object pattern is applicable
- {
- customer.PrintReceipt();
- }
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:
- class NullCustomer : customer
- {
- public NullCustomer()
- {
- this.Customername = "Invalid Customer";
-
- }
-
- }
Modified Main Method
- static void Main(string[] args)
- {
-
- var customer = ValidateCustomerBillpay(20, 1200);
- customer.PrintReceipt();
- }
-
- private static customer ValidateCustomerBillpay(int customerid, int paybillAmount)
- {
- customer inputCustomer =null;
- List<customer> customers = new List<customer>()
- {
- new RealCustomer(){Customerid = 1,Customername = "Rangesh"},
- new RealCustomer(){Customerid = 2,Customername = "Sripathi"},
- };
-
-
- foreach (var customer in customers.Where(customer => customer.Customerid == customerid))
- {
- inputCustomer = new RealCustomer() { Customerid = customer.Customerid, Customername = customer.Customername, billamount = paybillAmount };
- return inputCustomer;
- }
- return inputCustomer=new NullCustomer();
- }
Structure of NULL object Pattern