Liskov substitution principle (LSP) in C-Sharp

In previous articles we were learn about how and when to use Single responsibility principle (SRP) and Open closed principle (OCP) of SOLID. Today we are going to learn third principle of SOLID i.e. Liskov substitution principle (LSP).

According to LSP Objects should be replaceable with instances of their subtypes without altering the correctness of that program. In simple lines if we have two classes one is Base B and other are derived D1, D2 and D3 from Base B, then reference variable of B should be replaced with object of either D1 or D2 or D3 without affecting the results that we get with the base class instance, then the all classes conform to this principle.

public class B { } public class D1:B { } public class D2:B { } public class D3:B { } public class program { B objB = new B(); B obj = new D1(); B obj = new D2(); B obj = new D3(); }

Result from object of class B i.e. objB should be same for obj of D1,D2 and D3.

If you decide to apply Liskov substitution principle to your code, the behavior of your classes becomes more important than its code structure.

There is no easy way to follow this principle.

You need to implement your own logics to ensure that your code follows the Liskov Substitution Principle.

Problem: Following code Works well but with wrong output, i.e. behavior of class failed.

using System; namespace SOLID { public class Rectangle { protected int Width { get; set; } protected int Height { get; set; } public virtual void SetWidth(int width) { this.Width = width; } public virtual void SetHeight(int height) { this.Height = height; } public int GetArea() { return this.Height * this.Width; } } public class Square : Rectangle { public override void SetWidth(int width) { this.Width = width; this.Height = width; } public override void SetHeight(int height) { this.Height = height; this.Width = height; } } class Program { static void Main(string[] args) { Rectangle r = new Rectangle(); r.SetHeight(10); r.SetWidth(12); Console.WriteLine($"Area of rectangle is: {r.GetArea()}"); r = new Square(); r.SetHeight(10); r.SetWidth(12); Console.WriteLine($"Area of rectangle is: {r.GetArea()}"); Console.ReadKey(); } } }

OUTPUT: Area of rectangle is: 120 Area of rectangle is: 144

But account to Liskov substitution principle (LSP) result should be as follows.

OUTPUT: Area of rectangle is: 120 Area of rectangle is: 120

Another example: Lets again continue with Customer Class.

Suppose we have some Guest Accounts who only want know about interest on Amount. What will you do? A new class for new customer type Guest? Lets go with it as follows.

using System; namespace SOLID { public static class ErrorLogger { public static void ErrorHandler(string error) { System.IO.File.WriteAllText(@"c:\Error.txt", error); } } public class Customer { public virtual void AddCustomer() { try { // Business logic and database logic goes here. } catch (Exception ex) { ErrorLogger.ErrorHandler(ex.ToString()); } } public virtual double getInterest(double CurrentAmt) { return (CurrentAmt * 3) / 100; } } public class CoorporativeCustomer : Customer { public override void AddCustomer() { // Business logic and database logic goes here. } public override double getInterest(double CurrentAmt) { return (CurrentAmt * 3.5) / 100; } } public class SeniorCustomer : CoorporativeCustomer { public override void AddCustomer() { // Business logic and database logic goes here. } public override double getInterest(double CurrentAmt) { return (CurrentAmt * 4.5) / 100; } } public class GuestCustomer : Customer { public override void AddCustomer() { throw new Exception("Not allowed"); } public override double getInterest(double CurrentAmt) { return (CurrentAmt * 5) / 100; } } }

So we created a new class GuestCustomer inherit from Customer.

Problem:

Guest only have rights to view interest only. Should not have functionality to add a new account. We maintain it using exception. Suppose we want to create a collection with all type of customers as follows.

Add a new console application and copy above code in a new cs file.

Open program file and copy following code.

class Program { static void Main(string[] args) { List CustomerList = new List(); CustomerList.Add(new Customer()); CustomerList.Add(new SeniorCustomer()); CustomerList.Add(new CoorporativeCustomer()); CustomerList.Add(new GuestCustomer()); } }

Above code will compile and run successfully without any error. Because AddCustomer method was not invoked.

Let go ahead one step more.

class Program { static void Main(string[] args) { List CustomerList = new List(); CustomerList.Add(new Customer()); CustomerList.Add(new SeniorCustomer()); CustomerList.Add(new CoorporativeCustomer()); CustomerList.Add(new GuestCustomer()); foreach (Customer c in CustomerList) { c.AddCustomer(); } } }

Now above code will compile successfully again but throw exception at runtime.

AddCustomer method invoked with in foreach loop and it will raise exception.

Which shows GuestCustomer is not a Permanent Customer of the Bank.

So how we can implement Liskov Substitution Principle?

Solution:

Above example will throw an exception at compile time. Which means code not follows Liskov Substitution Principle.

To allow code to follow Liskov Substitution Principle, We need to separate those code which is or raising error from entire code.

To implement Liskov Substitution Principle we need to create two interfaces INewCustomers and IInterest one is for Interest Calculation and other for adding new customers,

we will Inherit Customer from INewCustomers, IInterest and GuestCustomer from IInterest as shown below.

using System; using System.Collections.Generic; namespace SOLID { public static class ErrorLogger { public static void ErrorHandler(string error) { System.IO.File.WriteAllText(@"c:\Error.txt", error); } } public interface INewCustomers { void AddCustomer(); } public interface IInterest { double getInterest(double CurrentAmt); } public class Customer : INewCustomers, IInterest { public virtual void AddCustomer() { try { // Business logic and database logic goes here. } catch (Exception ex) { ErrorLogger.ErrorHandler(ex.ToString()); } } public virtual double getInterest(double CurrentAmt) { return (CurrentAmt * 3) / 100; } } public class CoorporativeCustomer : Customer { public override void AddCustomer() { try { // Business logic and database logic goes here. } catch (Exception ex) { ErrorLogger.ErrorHandler(ex.ToString()); } } public override double getInterest(double CurrentAmt) { return (CurrentAmt * 3.5) / 100; } } public class SeniorCustomer : CoorporativeCustomer { public override void AddCustomer() { try { // Business logic and database logic goes here. } catch (Exception ex) { ErrorLogger.ErrorHandler(ex.ToString()); } } public override double getInterest(double CurrentAmt) { return (CurrentAmt * 4.5) / 100; } } public class GuestCustomer : IInterest { public double getInterest(double CurrentAmt) { return (CurrentAmt * 5) / 100; } } class Program { static void Main(string[] args) { List CustomerList = new List(); CustomerList.Add(new Customer()); CustomerList.Add(new SeniorCustomer()); CustomerList.Add(new CoorporativeCustomer()); //Compile time exception cannot convert GuestCustomer to cutomer //CustomerList.Add(new GuestCustomer()); foreach (Customer c in CustomerList) { c.AddCustomer(); } } } }

Above code follows the Liskov substitution principle(LSP).

In next article, we will learn Interface segregation principle (ISP).

If you have any query or question or topic on which, we might have to write an article for your interest or any kind of suggestion regarding this post, Just feel free to write us, by hit add comment button below or contact via Contact Us form.


Your feedback and suggestions will be highly appreciated. Also try to leave comments from your valid verified email account, so that we can respond you quickly.

 
 

{{c.Content}}

Comment By: {{c.Author}}  On:   {{c.CreatedDate|date:'dd/MM/yyyy'}} / Reply


Categories