Liskov Substitution Principle (LSP)

Liskov Substitution Principle (LSP)

Derived classes should be substitutable for their base classes without changing the expected behavior of the system.

Explanation:

If a class S is a subclass of class T, then objects of type T should be replaceable with objects of type S without breaking the behavior of the program.
The subclass must honor the expected behavior of the base class.

Why it's important:

  • Ensures reliable polymorphism
  • Prevents unexpected bugs when using inheritance
  • Makes code easier to maintain and extend

Payment Gateway System

Imagine building an e-commerce platform where customers can pay using different methods: Credit Card, UPI, or Wallet. You define a base class:

public class PaymentMethod

{

    public virtual void ProcessPayment(decimal amount)

    {

        // process payment logic

    }

}

Now you create specific payment types:

public class CreditCardPayment : PaymentMethod

{

    public override void ProcessPayment(decimal amount)

    {

        Console.WriteLine($"Paid {amount} via Credit Card.");

    }

}

 

public class UPIPayment : PaymentMethod

{

    public override void ProcessPayment(decimal amount)

    {

        Console.WriteLine($"Paid {amount} via UPI.");

    }

}

So far, all is good — both subclasses substitute the base class without changing behavior.

 

Violating LSP Example:

Now, someone adds a new subclass:

public class CODPayment : PaymentMethod

{

    public override void ProcessPayment(decimal amount)

    {

        throw new NotSupportedException("COD cannot be processed online.");

    }

}

This violates LSP, because code that expects PaymentMethod will crash when passed a CODPayment. For example:

List<PaymentMethod> payments = new List<PaymentMethod>

{

    new CreditCardPayment(),

    new UPIPayment(),

    new CODPayment() // Will throw at runtime

};

 

foreach (var method in payments)

{

    method.ProcessPayment(1000); // đŸ’„ COD breaks expectations

}

 

Solution (Follow LSP):

Refactor the design so that only supported payment types are processed:

public abstract class OnlinePaymentMethod

{

    public abstract void ProcessPayment(decimal amount);

}

 

public class CreditCardPayment : OnlinePaymentMethod

{

    public override void ProcessPayment(decimal amount)

    {

        Console.WriteLine($"Paid {amount} via Credit Card.");

    }

}

 

public class UPIPayment : OnlinePaymentMethod

{

    public override void ProcessPayment(decimal amount)

    {

        Console.WriteLine($"Paid {amount} via UPI.");

    }

}

 

// COD is no longer part of OnlinePaymentMethod

public class CODPayment

{

    public void PayOnDelivery(decimal amount)

    {

        Console.WriteLine($"Pay {amount} upon delivery.");

    }

}

Now OnlinePaymentMethod subclasses are 100% substitutable. Code expecting OnlinePaymentMethod will work safely with any derived class.

 

Summary

LSP ensures that subclasses behave as expected, avoiding runtime surprises or broken contracts. In real-world terms, if a system expects any PaymentMethod to "process payments," then all subclasses must actually do so — not throw errors or behave differently.

 

 

Comments

Popular posts from this blog

Multiline to singleline IN C# - CODING

EF Core interview questions for beginners

EF Core interview questions for experienced