The Liskov Substitution Principle states that subtypes must be substitutable for their base types. In order to substitute work, child class must not
- Remove base class behavior
- Violate base class invariants
In general calling code should not know they are different from base types. LSP suggests that IS-A relationship
should be replaced with IS-SUBSTITUTABLE-FOR
.
Let us take example of any web site subscription. Web site offer two types of customer, one is free and other is paid customer.
So we will be having an interface like below
public interface ICustomer { string CustomerName { set; get; } int CustomerCode { set; get; } int ProductQuantity { set; get; } double ProductRate { set; get; } CustomerType CustomerType { get; } double GetDiscount(); string PrintInvoice(double _amount); }
Two classes will be created for free and paid customer.
class FreeCustomerSalesEnity:ICustomer { private string _customername; private int _customercode; private int _productquantity; private double _productrate; public string CustomerName { set { _customername = value; } get { return _customername; } } public int CustomerCode { set { _customercode = value; } get { return _customercode; } } public int ProductQuantity { set { _productquantity = value; } get { return _productquantity; } } public double ProductRate { set { _productrate = value; } get { return _productrate; } } public CustomerType CustomerType { get { return CustomerType.Free; } } public double GetDiscount() { return 0; } public string PrintInvoice(double _amount) { throw new NotImplementedException(); } }
public enum CustomerType : int { Free, Paid } public class PaidCustomerSalesEnity : ICustomer { private string _customername; private int _customercode; private int _productquantity; private double _productrate; public string CustomerName { set { _customername = value; } get { return _customername; } } public int CustomerCode { set { _customercode = value; } get { return _customercode; } } public int ProductQuantity { set { _productquantity = value; } get { return _productquantity; } } public double ProductRate { set { _productrate = value; } get { return _productrate; } } public CustomerType CustomerType { get { return CustomerType.Paid; } } public double GetDiscount() { double rate = ProductQuantity * ProductRate; double discountamount = 0; double disrate = 20; discountamount = (disrate / 100) * rate; rate = rate - discountamount; return rate; } public string PrintInvoice(double _amount) { return "Product Invoice For Customer " + CustomerName + " with Total Amount " + _amount; } }
Code to add subscription for both classes.
ICustomer objIcust; List listcust = new List(); objIcust = new WithoutRefector.PaidCustomerSalesEnity(); objIcust.CustomerName = "Paid Customer"; objIcust.CustomerCode = 001; objIcust.ProductQuantity = 5; objIcust.ProductRate = 20; listcust.Add(objIcust); objIcust = new WithoutRefector.FreeCustomerSalesEnity(); objIcust.CustomerName = "Free Customer"; objIcust.CustomerCode = 002; objIcust.ProductQuantity = 5; objIcust.ProductRate = 20; listcust.Add(objIcust); string printinvoice = ""; foreach (ICustomer iCust in listcust) { double amount = iCust.GetDiscount(); printinvoice = iCust.PrintInvoice(amount); // Throw exception for free customer as it is not required there lstCustomerDiscount.Items.Add("Invoice Report –> " + printinvoice); }
Lets refactor above code to comply with Liskov Substitution principal.
Create seprate interface for FreeCustomer. This interface will not require discount and invoice functionality.
public interface IFreeCustomer { string CustomerName { set; get; } int CustomerCode { set; get; } CustomerType CustomerType { get; } }
public class FreeCustomerSalesEntity : IFreeCustomer { private string _customername; private int _customercode; private int _productquantity; private double _productrate; public string CustomerName { set { _customername = value; } get { return _customername; } } public int CustomerCode { set { _customercode = value; } get { return _customercode; } } public int ProductQuantity { set { _productquantity = value; } get { return _productquantity; } } public double ProductRate { set { _productrate = value; } get { return _productrate; } } public CustomerType CustomerType { get { return CustomerType.Free; } } }
Create IPaidCustomer, which will implement IFreeCustomer and IDiscount interface.
public interface IDiscount { double GetDiscount(); }
public interface IPaidCustomer : IFreeCustomer, IDiscount { int ProductQuantity { get; set; } double ProductRate { get; set; } string PrintInvoice(double _amount); }
public class PaidCustomerSalesEntity : IPaidCustomer { private string _customername; private int _customercode; private int _productquantity; private double _productrate; public CustomerType CustomerType { get { return CustomerType.Paid; } } public string CustomerName { set { _customername = value; } get { return _customername; } } public int CustomerCode { set { _customercode = value; } get { return _customercode; } } public int ProductQuantity { set { _productquantity = value; } get { return _productquantity; } } public double ProductRate { set { _productrate = value; } get { return _productrate; } } public double GetDiscount() { if (CustomerType == CustomerType.Paid) { double rate = ProductQuantity * ProductRate; double discountamount = 0; double disrate = 20; discountamount = (disrate / 100) * rate; rate = rate - discountamount; return rate; } return 0; } public string PrintInvoice(double _amount) { return "Product Invoice For Customer " + CustomerName + " with Total Amount " + _amount; } }
Above paid class implementing discount and invoice printing functionalities. Thease classes will be used like this
IPaidCustomer objIcust; List listcust = new List(); objIcust = new RefectorCode.PaidCustomerSalesEnity(); objIcust.CustomerName = "Paid Customer"; objIcust.CustomerCode = 001; objIcust.ProductQuantity = 5; objIcust.ProductRate = 20; listcust.Add(objIcust); //Paid customer is converted as Free Customer IFreeCustomer obj = new FreeCustomerSalesEntity(); obj.CustomerName = "Free Customer"; obj.CustomerCode = 002; listcust.Add(obj); string printinvoice = ""; foreach (IFreeCustomer iCust in listcust) { lstCustomerDiscount.Items.Add("Invoice Report –> " + printinvoice); }
Full code for above priciple is available here.